在去年的这个时候,本骚年还在被Grunt和Gulp以及各种Requirejs、Seajs团团围住攻击,狼狈不堪。后面认识了Webpack之后,基本所有项目框架都拿它来构建了。
当然也不包括本骚年负责项目都是纯前端的PC端单页应用的原因,还没遇到什么项目使用Webpack上太难的问题。
Webpack是一个现代的JavaScript应用程序的模块打包器(module bundler)。其实Webpack不应该拿来跟Grunt/Gulp比较的,但在本骚年这边它就是承担起了很大一部分工作。
这里主要基于Webpack2来讲吧,Webpack1迁移到2还是不是特别难的,官方也配了迁移文档。 其实官方的文档也有很详细的说明了,对于一般的项目还是可以完全驾驭的。
下面我们先跟随着原始的脚步过一遍概念吧。
四个核心概念:入口(entry)、输出(output)、loader、插件(plugins)。
将您应用程序的入口起点认为是根上下文(contextual root)或app第一个启动文件。
一般来说,在Angular中我们将是启动.bootstrap()
的文件,在Vue中则是new Vue()
的位置,在React中则是ReactDOM.render()
或者是React.render()
的启动文件。
123 | module.exports = {entry: './path/to/my/entry/file.js'}; |
---|
同时,entry
还可以是个数组,这个时候「文件路径(file path)数组」将创建“多个主入口(multi-main entry)”。
常见的使用方式是我们需要把”babel-polyfill.js”这样的文件也注入进去(如果需要React的话还可以加个”react-hot-loader/patch”进去):
123 | module.exports = {entry: ['babel-polyfill', './path/to/my/entry/file.js']}; |
---|
output属性描述了如何处理归拢在一起的代码(bundled code),在哪里打包应用程序。一般需要以下两点:
123456 | module.exports = {output: {filename: 'bundle.js',path: '/home/proj/public/assets'}}; |
---|
webpack把每个文件(.css, .html, .scss, .jpg, etc.) 都作为模块处理。但webpack只理解JavaScript。
如果你看过生成的bundle.js
代码就会发现,Webpack将所有的模块打包一起,每个模块添加标记id,通过这样一个id去获取所需模块的代码。
而我们的loader的作用,就是把不同的模块和文件转换为这样一个模块,打包进去。
loader支持链式传递。能够对资源使用流水线(pipeline)。loader链式地按照先后顺序进行编译,从后往前,最终需要返回javascript。
不同的应用场景需要不同的loader,这里我简单介绍几个(loader使用前都需要安装,请自行查找依赖安装):
1. babel-loader
官网在此,想要了解的也可以参考Babel 入门教程。
babel-loader
将ES6/ES7语法编译生成ES5,当然有些特性还是需要babel-polyfill
支持的(Babel默认只转换新的JavaScript句法,而不转换新的API,如Promise等全局对象)。
12345678 | // 在webpack1里使用loader属性,在webpack2中为rules属性module.exports = {module: {rules: [{test: /\.(js)$/, use: 'babel-loader'}]}}; |
---|
而对于babel-loader的配置,可以通过options
进行,但一般更常使用.babelrc
文件进行:
1234 | {"presets": [],"plugins": [] // 插件} |
---|
presets
: 设定转码规则有”es2015”, “stage-0/1/2/3”,如果你使用React则还加上”react”,而我一般使用”lastest”装满最新特性。 当然这些都需要安装,你选择了对应的转码规则也要安装相应的依赖:
1 | npm install --save-dev babel-preset-latest |
---|
2. ts-loader
一看就知道,是个typescript的loader,同样的还有awesome-typescript-loader,关于两者的不同可参考作者的话。 这里我们使用ts-loader也就足够了:
12345 | {test: /\.(ts)$/,use: ["babel-loader", "ts-loader"],exclude: /node_modules/} |
---|
-webkit-
、-moz-
等)<img src="image.png">
为require(“./image.png”),需要在配置中指定image文件的加载器loader仅在每个文件的基础上执行转换,插件目的在于解决loader无法实现的其他事。
由于plugin可以携带参数/选项,需要在wepback配置中,向plugins属性传入new
实例。
这里也介绍几个常用的插件:
1. HtmlwebpackPlugin
功能有下:
但其实最常使用的,无非是把index.htnm
页面插入(因为入口文件为js文件):
1234 | new HtmlwebpackPlugin({template: path.resolve(__dirname, 'src/index.html'),inject: 'body'}) |
---|
2. CommonsChunkPlugin
提取代码中的公共模块,然后将公共模块打包到一个独立的文件中,以便在其他的入口和模块中使用。
像之前有个项目有远程服务器调试代码的需求[捂脸],这时候我们需要把依赖单独抽离出来(不然文件太大了):
1234567 | new CommonsChunkPlugin({name: 'vendors',filename: 'vendors.js',minChunks: function(module) {return isExternal(module);}}) |
---|
关于isExternal()
函数,用了很简单的方式进行:
1234567 | function isExternal(module) {var userRequest = module.userRequest;if (typeof userRequest !== 'string') {return false;}return userRequest.indexOf('node_modules') >= 0; // 是否位于node_modules里} |
---|
3. webpack.ProvidePlugin
定义标识符,当遇到指定标识符的时候,自动加载模块。像我们常用的jQuery:
1234 | new webpack.ProvidePlugin({jQuery: 'jquery',$: 'jquery'}) |
---|
4. ExtractTextPlugin
可以将样式从js中抽出,生成单独的.css
样式文件(同样因为方便调试[捂脸+1])。即把所以的css打包合并:
123 | new ExtractTextPlugin('style.css', {allChunks: true // 提取所有的chunk(默认只提取initial chunk,而上面CommonsChunkPlugin已经把部分抽离了)}) |
---|
这些选项能设置模块如何被解析。这里本骚年只讲两个常用的:
1. resolve.extensions
自动解析确定的扩展。默认值为:
1234567 | resolve: {extensions: [".js", ".json"]// 当我们需要使用typescript的时候,需要修改:extensions: [".js", ".ts"]// 当我们需要使用React时,还要修改:extensions: ['.ts', '.tsx', '.js']} |
---|
2. resolve.alias
创建import
或require
的别名,来确保模块引入变得更简单。
12345 | resolve: {alias: {shared: path.resolve(__dirname, 'src/shared'),}} |
---|
如果使用typescript的话,我们还需要配置tsconfig.json
:
1234567 | {"compilerOptions": {"paths": {"shared": ["src/shared"]}}} |
---|
这样我们就可以跟长长的路径定位说拜拜了:
1234 | // 原代码import {something} from '../../../../../shared/something';// 配置后import {something} from 'shared/something'; |
---|
此选项控制是否生成,以及如何生成source map。
要开启source map,我们还需要安装source-map-loader
:
1 | npm i -D source-map-loader |
---|
同时添加loader的配置:
123456789101112 | module.exports = {module: {rules: [{test: /\.js$/,use: ["source-map-loader"],enforce: "pre"}]},devtool: 'source-map' // 然后我们就可以开启了}; |
---|
webpack-dev-server
是webpack官方提供的一个小型Express服务器,主要提供两个功能:
在实际开发中,webpack-dev-server
可以实现以下需求:
一般来说,我们可以通过引入webpack.config.js
文件然后调整配置就可以启用了:
123456789 | // webpackServer.config.js文件// 生成配置var webpack = require('webpack');var path = require('path'); // 引入node的path库var config = require("./webpack.config.js"); // 引入webpack.config.jsconfig.entry.concat(['webpack/hot/dev-server','webpack-dev-server/client?http://localhost:3333']);module.exports = config; |
---|
然后命令行启动:
1 | webpack-dev-server --config webpackServer.config.js --host 0.0.0.0 --port 3333 --devtool eval --progress --colors --hot --content-base dist |
---|
webpack-dev-server --config webpackServer.config.js
后面的都是配置,也可以在webpackServer.config.js文件中写入。
常用配置:
devServer.contentBase
: 告诉服务器从哪里提供内容devServer.port
(CLI): 指定要监听请求的端口号devServer.host
(CLI): 指定使用一个host。默认是localhostdevServer.hot
: 启用webpack的模块热替换特性devServer.progress
(CLI): 将运行进度输出到控制台。其余配置请移步官方文档。
这里本骚年就不一个个讲解了,简单分享几个用过的webpack.config.js
配置吧。
123456789101112131415161718192021222324252627282930313233343536 | var webpack = require('webpack');var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');var ExtractTextPlugin = require('extract-text-webpack-plugin');var path = require('path');var config = {entry: {app: ['./app/bootstrap.js'] // 入口文件},output: {path: path.resolve(__dirname, 'app/entry'), // 编译后文件publicPath: '/entry/',filename: 'bundle.js' // 生成文件名},module: {loaders: [{test: /\.js$/,loader: 'babel-loader',exclude: /node_modules/}]},plugins: [// 使用CommonChunksLoader来提取公共模块new CommonsChunkPlugin({name: 'vendors',filename: 'vendors.js',minChunks: function (module) {var userRequest = module.userRequest;if (typeof userRequest !== 'string') {return false;}return userRequest.indexOf('node_modules') >= 0}})]};module.exports = config; |
---|
1234567891011121314151617181920212223242526272829303132333435363738 | var webpack = require('webpack');var path = require('path'); //引入node的path库var HtmlwebpackPlugin = require('html-webpack-plugin');var config = {entry: ['webpack/hot/dev-server','webpack-dev-server/client?http://localhost:3000',path.resolve(__dirname, 'app/index.js')], //入口文件output: {path: path.resolve(__dirname, 'dist'), // 指定编译后的代码位置为 dist/bundle.jsfilename: 'bundle.js'},module: {loaders: [// 为webpack指定loaders{test: /\.less$/,loaders: ['style', 'css', 'less'],include: path.resolve(__dirname, 'app')},{test: /\.jsx?$/,loader: 'babel-loader',exclude: 'node_modules'}]},plugins: [// 入口模板文件解析new HtmlwebpackPlugin({title: 'React Redux Test',template: path.resolve(__dirname, 'templates/index.ejs'),inject: 'body'})],devtool: 'source-map'}module.exports = config; |
---|
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | var webpack = require('webpack');var path = require('path');var HtmlwebpackPlugin = require('html-webpack-plugin');var config = {entry: ['babel-polyfill', './src/bootstrap.ts'],output: {path: path.resolve(__dirname, 'dist'),filename: './bundle.js'},resolve: {extensions: ['.ts', '.js']},module: {rules: [{test: /\.ts$/,use: ["babel-loader", "ts-loader", "angular2-template-loader"],exclude: /node_modules/},{test: /\.(html|css)$/,use: ['raw-loader'],exclude: [path.resolve(__dirname, 'src/index.html')]},{test: /\.async\.(html|css)$/,loaders: ['file?name=[name].[ext]']}, {test: /\.js$/,use: ["source-map-loader"],enforce: "pre"}]},plugins: [new HtmlwebpackPlugin({template: path.resolve(__dirname, 'src/index.html'),inject: 'body'}),new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/,path.resolve(__dirname, 'src'),{})],devtool: 'source-map'};module.exports = config; |
---|
很可惜,当时玩Vue本骚年是用的vue-cli
,所以这里没有Vue相关的代码。不过经过上面的讲解以及课后的练习,相信你一定可以搭建自己想要的应用。
Webpack的资源很多,而深入理解的你也能去开发自己想要的loader或是插件的,多了解多尝试总是很棒的。
文章来源:腾讯工程师 王贝珊