前端技术发展迅猛,各种可以提高开发效率的新思想和框架层出不穷,但是它们都有一个共同点,即源代码无法直接运行,必须通过转换后才可以正常运行。webpack是目前主流的打包模块化JavaScript的工具之一。
本章主要涉及的知识点有:
l 什么是webpack
l webpack的作用
l webpack的配置
l webpack-dev-server
以前,一个网站可能只是由几个HTML和CSS文件构成,在某些情况下可能还有一个或几个JavaScript文件。但随着前端技术的发展,这一切都被改变。
整个开发社区一直致力于改善用户和开发人员在使用和构建JavaScript/Web应用程序方面的整体体验。因此,在项目中引入了许多新的库和框架。
一些设计模式也随着时间的推移而演变,为开发人员提供了一种更好、更强大但非常简单的编写复杂JavaScript应用程序的方法。网站不再只是一个包含几个文件的程序包,随着JavaScript模块的引入,编写封装的小块代码成为一种新趋势。但随之而来的是网站代码的体积越来越大,而且JavaScript版本不断升级,新的API层出不穷,在开发人员编写的代码类型和浏览器能够理解的代码类型方面也存在巨大差异。开发人员必须使用大量被称为polyfills的辅助代码,以确保浏览器能够解析其中的代码。
为了解决这些问题,开发了一系列构建工具,它们各有优缺点。由于前端工程师很熟悉JavaScript,Node.js又可以满足所有构建需求,因此大多数构建工具都是用Node.js开发的。
1. Grunt
Grunt是一个任务执行者,它有大量现成的插件封装了常见的任务,也能管理任务之间的依赖关系,自动化地执行依赖的任务。每个任务的具体执行代码和依赖关系写在配置文件Gruntfile.js里,代码如下:
module.exports = function(grunt) {
grunt.initConfig({
uglify: {
app_task: {
files: {
'build/app.min.js': ['lib/index.js', 'lib/test.js']
}
}
},
watch: {
another: {
files: ['lib/*.js'],
}
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('dev', ['uglify','watch']);
};
其中,uglify和watch都是插件,分别用于压缩和监听自动刷新,grunt.loadNpmTasks用于加载插件;grunt.registerTask用于执行任务。在项目根目录下执行命令grunt dev,就会启动JavaScript文件压缩和自动刷新功能。
Grunt的优点是灵活,它可以执行自定义的任务,并且有大量的可复用的插件封装好了常见的构建任务。
Grunt的缺点是集成度不高,要写很多配置后才可以用,无法做到开箱即用。
2. Gulp
Gulp是一个基于流的自动化构建工具,除了可以管理和执行任务之外,还支持监听文件、读写文件。Gulp被设计得非常简单,只通过5种方法就可以支持几乎所有的构建场景:gulp.task用于注册一个任务,gulp.run用于执行任务,gulp.watch用于监听文件的变化,gulp.src用于读取文件,gulp.dest用于写文件。
Gulp的最大特点是引入了流的概念,同时提供了一系列常用的插件去处理流,流可以在插件之间传递。Gulp的示例代码如下:
01 var gulp = require('gulp');
02 var jshint = require('gulp-jshint');
03 var sass = require('gulp-sass');
04 var concat = require('gulp-concat');
05 var uglify = require('gulp-uglify');
06 gulp.task('sass', function() {
07 gulp.src('./scss/*.scss')
08 .pipe(sass())
09 .pipe(gulp.dest('./css'));
10 });
11 gulp.task('scripts', function() {
12 gulp.src('./js/*.js')
13 .pipe(concat('all.js'))
14 .pipe(uglify())
15 .pipe(gulp.dest('./dist'));
16 });
17 gulp.task('watch', function(){
18 gulp.watch('./scss/*.scss', ['sass']);
19 gulp.watch('./js/*.js', ['scripts']);
20 });
代码解析:
- 第01~05行引入Gulp和相关插件。
- 第06~10行通过gulp.task('sass', function() {}编译SCSS任务。其中第07行通过gulp.src读取文件,第08行通过pipe管道加载插件,第09行输出CSS文件。
- 第11~16行通过gulp.task('scripts', function() {}合并压缩JavaScript文件。
- 第17~20行通过gulp.task('watch', function() {}监听文件的变化。
Gulp的优点是好用又不失灵活,既可以单独完成构建,也可以和其他工具搭配使用。其缺点和Grunt类似,集成度不高,要写很多配置后才可以用,无法做到开箱即用。
可以将Gulp看作Grunt的加强版。相对于Grunt,Gulp增加了监听文件、读写文件、流式处理的功能。
3. webpack
webpack是一个打包模块化JavaScript的工具,在webpack里一切文件皆模块,通过Loader转换文件,通过Plugin注入钩子,最后输出由多个模块组合成的文件。webpack专注于构建模块化项目。
webpack具有很大的灵活性,通常在项目中编写webpack.config.js配置处理文件的方式,示例代码如下:
module.exports = {
entry: './app.js',
output: {
filename: 'bundle.js'
}
}
其中,entry是所有模块的入口,webpack从入口开始递归解析出所有依赖的模块;output将入口所依赖的所有模块打包成一个bundle.js文件并输出。
webpack专注于处理模块化的项目。通过webpack,现在使用React、Vue、Angular等搭建的项目能做到开箱即用、一步到位,并且可以通过Plugin扩展很多功能。webpack社区庞大而活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展。
简单来说,webpack会遍历项目中的所有文件,如JavaScript、CSS、SCSS、图片、模板等,并创建它们的依赖关系图,该依赖关系图用于描述各个模块之间的依赖关系。根据依赖关系图,对项目中的各模块进行组合和打包,形成一个bundle.js文件,在这个文件中可以很容易地插入HTML文件并用于应用程序。
webpack在打包的同时还会对各文件进行编译。把浏览器不能识别的语法(例如ES 6的语法、Node.js的模块化、Sass)编译成CSS,将TypeScript编译成JavaScript等,转换成浏览器能够识别的语法。
webpack在打包时可以根据配置对代码进行压缩、优化,以减小网络传输过程中文件的体积。最典型的做法是把所有变量名都精简为一个字母,所有的换行符和缩进都去掉。如图8.1所示是webpack打包后的JS文件。
webpack的功能还有很多,例如代码分割、自动刷新等。代码分割就是提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。自动刷新是指监听本地源代码的变化,自动重新构建、刷新浏览器。
webpack官方网站首页中的一张图片形象地展示了webpack的作用,如图8.2所示。
目前,随着单页应用的流行,大多数团队在开发新项目时会采用“模块化+新语言+新框架"的方式。webpack可以为这些模块化项目提供一站式的解决方案、良好的生态链和维护团队,从而提供良好的开发体验并保证项目质量。
webpack的配置项很多,具体可以参考官方网站文档(https://webpack.js.org/concepts/)。本节介绍几个重要且常用的配置项。
在对webpack进行配置之前,首先要安装webpack。在终端输入如下代码:
npm install webpack webpack-cli --save-dev
webpack-cli是一个用来在命令行中运行webpack的工具,可以用来处理命令行参数。
通常在项目根目录下增加webpack.config.js文件,在该文件中编写代码进行webpack的配置。例如,在webpack.config.js文件中添加以下代码:
01 const path = require('path');
02 const HtmlWebpackPlugin = require('html-webpack-plugin');
03
04 module.exports = {
05 entry:'./src/index.js',
06 output:{
07 path: path.join(__dirname, '/dist'),
08 filename: 'bundle.js'
09 },
10 module: {
11 rules: [
12 {
13 test: /\.(js|jsx)$/,
14 exclude: /node_modules/,
15 use: {
16 loader: 'babel-loader'
17 }
18 }
19 ]
20 },
21 plugins: [
22 new HtmlWebpackPlugin({
23 template: './src/index.html'
24 })
25 ]
26 }
代码解析:
- 第01行引入Node.js内置模块path,用于获取文件路径并更改文件的位置。
- 第02行引入插件html-webpack-plugin,用于生成一个HTML文件,这个HTML文件用于引用打包后的JavaScript文件。因为JavaScript文件必须放到HTML文件中才可以在浏览器中执行。
- 从第04行开始定义module.exports对象,其中包含一些属性,例如entry、output、module、plugins等。
- 第05行的entry属性用于指定webpack应该从哪个文件开始,以便创建内部依赖关系图。这里是src下的index.js文件。
- 第06行的output属性指定应该在哪里生成打包后的文件以及打包后的文件的名称。output.path用于指定文件存储位置,output.filename用于指定文件名称。
- 第10~20行的module指定需要哪些loader。loader指定webpack应该怎样处理特定类型的文件。注意,webpack默认只理解JavaScript和JSON类型的文件,对于项目中使用的其他类型文件或语言,例如PNG、JPG、GIF格式的图片文件或者SCSS、LESS文件,需要在这里加载相应的loader插件进行处理。此处用正则表达式匹配所有后缀是.js或.jsx的文件,且排除/node_modules/下的文件,使用babel-loader对这些文件进行编译处理。还有很多别的loader插件,例如处理CSS和SCSS文件的style-loader、css-loader、sass-loader,处理图片的url-loader等。
- 第21~25行的plugins是插件属性,用于扩展webpack的功能。这里使用html-webpack-plugin插件将src/index.html作为模板文件,所有打包后的文件都将自动放入该HTML文件中。html-webpack-plugin插件还有更多的配置,具体可以参考官方文档。插件还有mini-css-extract-plugin,用于将CSS提取到单独的文件中,即为每个包含CSS的JavaScript文件创建一个CSS文件,terser-webpack-plugin用于压缩JavaScript文件。
最后,要处理ES 6代码。ES 6是2015年发布的JavaScript语言标准,它引入了新的语法和API,例如class、let、for...of、promise等,但是这些JavaScript新特性只被最新版本的浏览器支持。低版本浏览器并不支持。因此,低版本浏览器需要一个转换工具,把ES 6代码转换成浏览器能识别的代码。Babel就是这样的一个工具,它是JavaScript语法的编译器。
Babel工具在使用之前需要先进行配置。
首先在项目根目录下创建一个.babelrc文件,它是一个JSON格式的文件,用来保存Babel工具的配置。webpack中的babel-loader加载后会从项目根目录下的.babelrc文件中读取配置。
在.babelrc配置文件中,主要是对预设(presets)和插件(plugins)进行配置。.babelrc配置文件一般如下:
{
"presets": [
"es2015",
"react"
]
}
其中,presets属性告诉Babel要转换的源代码使用了哪些新的语法特性,它是一组Plugins的集合。上面的代码表示先用React转换,然后用ES 2015(ES 6)转换。
以上配置中用到的loader或plugins,如果不是webpack内置的,那么在使用前需要先安装,例如编译CSS和SCSS文件的loader,使用npm install css-loader style-loader sass-loader --save-dev进行安装。
webpack配置好以后,可以在package.json文件中增加如下代码:
"scripts": {
"dev": "webpack --config webpack.config.js"
},
在scripts属性中,npm允许通过名称引用本地安装的Node.js包。上面的代码表示在开发dev模式下运行webpack,在终端命令行中输入npm run dev时执行webpack --config webpack.config.js。
在实际项目中,需要根据特定情况使用不同的配置文件,例如,开发时执行webpack.dev.config.js;当需要打包部署到生产环境时,执行webpack.prod.config.js。这些webpack文件的配置项也要根据用途进行不同的配置,例如开发环境时需要配置webpack-dev-server,但是生产环境时需要配置CSS、JS压缩等。
到目前为止,每次进行更改都需要重新构建代码。对于开发中频繁修改代码并且要即时查看页面情况来说,这是非常麻烦且影响开发效率的。
webpack提供了一个实时重新加载的Web服务器——webpack-dev-server,它可以自动构建和刷新页面。可以在终端输入下面的代码安装:
npm install webpack-dev-server --save-dev
安装后需要更新package.json中script的dev,代码如下:
"dev": "webpack serve --mode development"
在webpack.config.js中增加以下代码:
devServer: {
hot: true,
port: 8080,
compress: true,
headers: {
'Access-Control-Allow-Origin': '*'
},
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '' },
},
}
}
代码解析:
- Hot:true表示启用模块热替换,该功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面,可以加快开发速度。
- port指定监听请求的端口号。
- compress:true表示启用gzip压缩。
- headers表示为所有响应添加headers。
- 'Access-Control-Allow-Origin':'*'表示允许所有域名跨域访问。
- proxy用来设置代理。上面的代码中,假如有接口/api/users,它实际上会将请求代理到http://localhost:3000/users。
pathRewrite表示将接口中的/api替换为空。
本节选自《React.js+Node.js+MongoDB企业级全栈开发实践》,获出版社和作者授权共享。