webpack 使用总结
12月27日, 2016 JavaScript 软件分享 Tumars 10,303 views次
12月27日, 2016 10,303 views次
前言
本文是对近半年使用 webpack 的一个总结。webpack 作为 配置型的工具,虽然配置项很多、功能强大,但是用起来并不复杂。文内主要内容来自网络摘抄以及翻译官网文档,相对比较全面。
目前 webpack 已经更新到 2.x 版本,不过 1.x 与 2.x 两个大版本之间的区别并不大。本文主要是总结对 1.x 版本的使用。
本文的 配套 DEMO 请访问 https://github.com/tumars/boilerplate-webpack-react-es6-cssModule 查看。
本文日后还会更新修改,如若转载请附上原文地址:http://www.ferecord.com/webpack-summary.html,以便溯源。
目录
什么是 webpack
Webpack 是德国开发者 Tobias Koppers 开发的一个强力的模块打包器。 所谓包(bundle)就是一个 JavaScript 文件,它把一堆资源(assets)合并在一起,以便它们可以在同一个文件请求中发回给客户端。 包中可以包含 JavaScript、CSS 样式、HTML 以及很多其它类型的文件。
为什么要使用 webpack
- 与 react 一类模块化开发的框架搭配着用比较好。
- 属于配置型的构建工具,容易上手,160 行代码可大致实现 gulp 400 行才能实现的功能。
- webpack 使用内存来对构建内容进行缓存,构建过程会比较快。
webpack 的特点
1. 能够实现多种不同的前端模块系统
前端页面越来越复杂、代码量变大,我们需要使用一些模块系统来组织代码、把代码分割成不同的模块来使用。
目前前端模块系统主要分为以下几个标准:
- Script标签形式
- CommonJS
- AMD和一些变种实现
- CMD
- ES6模块
以上方式有各自的优缺点,这里不做赘述。
webpack 作为一个智能解析器,可以处理几乎任何第三方的库,无论他们的模块形式是 commonjs,amd 还是普通的 js 文件,甚至加载依赖的时候可以动态表达式:
1 2 3 4 5 6 |
import React, { PropTypes } from 'react' require('es6-promise').polyfill() require("./templates/" + name + ".jade") var $ = require('jQuery') |
2. 分块打包
资源请求是个老生常谈的优化问题,有两个极端的方向来处理资源:
- 将所有文件都打包在一个请求里
- 每个模块都发起一个请求
webpack 打包前端资源(模块)时能够实现代码分割,按需加载,如在单页面应用里,将每个 page 的资源单独打包,按页面加载。这种方式被称为 code splitting(具体实现方式查看:https://webpack.github.io/docs/code-splitting.html。
3. 处理所有资源
目前模块系统只能处理 javascript,而我们还有很多其他静态资源需要处理,比如:
- 预编译的 js 文件(coffeescript 或 jsx 或 es6 -> javascript)
- css 文件 (less -> css -> autoprefixer)
- 图片 (压缩、转 base64)
- 模板 (jade、各种template)
- 等等
配置 webpack 后,这些都很容易处理。
webpack 与 、requirejs 等工具的区别
gulp、grunt 是自动化任务构建工具。
webpack 是模块化解决方案。
gulp 是通过一系列插件将原本复杂繁琐的任务(Task)自动化,并不能将你的 css 等非 js 资源模块化,它与 webpack 之间有一定的功能重合,比如打包、压缩混淆、图片转 base64 等等。但它们的目的跟要解决的问题是不一样的。
webpack 本质上是类似 browserify、seajs 、requirejs 的JS模块化的方案。其中 seajs / require 是一种类型,browserify / webpack 是另一种类型。
seajs / require : 是一种在线”编译” 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。
browserify / webpack : 是一种预编译模块的方案,相比于上面 ,这个方案更加智能。这里以webpack为例。首先,它是预编译的,不需要在浏览器中加载解释器。另外,你在本地直接写JS,不管是 AMD / CMD / ES6 风格的模块化,它都能认识,并且编译成浏览器认识的JS。
webpack 的配置
首先看一个简单的 webpack 配置图。
webpack.config.js:
Webpack的配置主要为了这几个项目:
devtool
devtool 属性可以配置调试代码的方式,有多种调试方式。devtool 一般只在开发时使用,生产环境下应将值设为 false。常用的值为以下两个:
eval
可以设断点调试,不显示列信息,每个js模块代码用eval()执行,并且在生成的每个模块代码尾部加上注释,不会生成.map文件。source-map
可以设断点调试,不显示列信息,生成相应的.Map文件,并在合并后的代码尾部加上注释//# sourceMappingURL=**.js.map ,可以看到模块代码并没有被eval()包裹,此种模式并没有将调试信息放入D打包后的代码中,保持了打包后代码的简洁性.
其他还有eval-source-map
、cheap-source-map
、cheap-module-source-map
、cheap-module-eval-source-map
、hidden-source-map
,也可以自己组合,如cheap-module-eval-source-map
。
1 2 3 4 5 6 |
// webpack.config.js module.exports = { devtool: 'cheap-module-eval-source-map' }; |
据说cheap-module-eval-source-map
绝大多数情况下都会是最好的选择,这也是下版本 webpack 的默认选项。
具体使用参考官网 devtool 部分
entry 和 output
entry 用来定义入口文件,可以是个字符串或数组或者对象。
当 entry 是个字符串的时候:
1 2 3 4 5 |
module.exports = { entry: './main.js' }; |
当 entry 是个数组的时候,里面同样包含入口 js 文件,另外一个参数可以是用来配置 webpack 提供的一个静态资源服务器,webpack-dev-server。webpack-dev-server 会监控项目中每一个文件的变化,实时的进行构建,并且自动刷新页面:
1 2 3 4 5 6 7 8 |
module.exports = { entry: [ 'webpack/hot/only-dev-server', './main.js' ] }; |
当 entry 是个对象的时候,我们可以将不同的文件构建成不同的文件,按需使用:
1 2 3 4 5 6 7 8 |
module.exports = { entry: { a: './a.js', b: './b.js' } }; |
output 用于定义构建后的文件的输出,是个对象。其中包含path
和filename
:
1 2 3 4 5 6 7 8 |
module.exports = { output: { path: './build', filename: 'bundle.js' } }; |
如果有多个入口文件分开打包,可以通过[name]
来命名打包的输出文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module.exports = { entry: { a: "./a", b: "./b", c: ["./c", "./d"] }, output: { path: path.join(__dirname, "dist"), filename: "[name].entry.js" } }; |
module
module 主要是用来配置加载器(Loaders),包括loaders
、preLoaders
、postLoaders
、noParse
。
webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换,本文第六章将会介绍一些常用的 Loaders。
loaders
、preLoaders
、postLoaders
的配置选项包括以下几方面:
test
: 一个匹配loaders所处理的文件的拓展名的正则表达式(必须)loader
: loader的名称(必须)include/exclude
: 手动添加必须处理的文件/文件夹,或屏蔽不需要处理的文件/文件夹(可选)query
: 为loaders提供额外的设置选项(可选)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module.exports = { module: { preLoaders: [ {test: /\.js$/, loader: "eslint-loader", exclude: /node_modules/} //打包前使用eslint检测js ], loaders: [ {test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, //处理css文件,把less编译成css {test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} // 处理图片,大小低于8k的文件编译为base64 ] } }; |
noParse
是 webpack 的一个很有用的配置项,如果你确定一个模块中没有其它新的依赖就可以配置这项,webpack 将不再扫描这个文件中的依赖。
1 2 3 4 5 |
module: { noParse: [/moment-with-locales/] } |
具体使用参考官网 module 部分
官网也收集了常用的 Loaders 列表:list-of-loaders
resolve
resolve 用来配置文件路径的指向。可以定义文件跟模块的默认路径及后缀等,节省 webpack 搜索文件的时间、优化引用模块时的体验。常用的包括alias
、extensions
、root
、modulesDirectories
属性:
alias
:是个对象,把资源路径重定向到另一个路径,比如require('React')
默认引用的是/node_modules/react.js
,我们可以定义用react.min.js
替代
1 2 3 4 5 6 7 8 9 10 11 |
//webpack.config.js ... resolve: { alias: { 'someutil': path.join(__dirname, './src/util/someutil'), 'react-dom': path.join(nodeModulesPath,'react-dom/dist/react-dom.min') } } ... |
1 2 3 4 5 6 7 8 9 10 |
//app.js //配置resolve.alias前 import react-dom form 'react-dom' //此时默认载入'/node_modules/react-dom/dist/react-dom.js' import someutil form '../../src/util/someutil' //需要定位该文件相对位置 //配置resolve.alias后 import react-dom form 'react-dom' //此时载入压缩后的'react-dom.min.js' import someutil form 'someutil' //不需要再写相对位置,因为已在resolve.alias中定义 |
extensions
:是个数组,定义资源的默认后缀,比如定义后引用a.js、b.json、c.css
等资源可以不用写后缀名直接写a、b、c
1 2 3 4 5 6 7 8 |
//webpack.config.js ... resolve: { extensions: ['', '.js', '.jsx', '.json', '.css'] } ... |
1 2 3 4 5 6 7 8 9 10 11 12 |
//app.js //配置resolve.extensions前 import a form 'a.js' import b form 'b.json' import c form 'c.css' //配置resolve.extensions后 import a form 'a' import b form 'b' import c form 'c' |
root
:是个数组,通过绝对路径的方式来定义查找模块的文件夹。可以是一个数组,主要是用来增加模块的搜寻位置使用的。root 的值必须是绝对路径,使用path.resolve
设置。
1 2 3 4 5 6 7 8 9 10 11 |
//webpack.config.js var path = require('path'); resolve: { root: [ path.resolve('./app/modules'), path.resolve('./vendor/modules') ] } //这样设置后,会增加搜索app/modules和vendor/modules下所有node_modules里面的模块。 |
modulesDirectories
:是个数组,是用来设置搜索的目录名的,默认值:["web_modules", "node_modules"]
。如果把值设置成["mydir"]
, webpack会查询“./mydir”, “../mydir”, “../../mydir”
等。
1 2 3 4 5 6 7 8 |
//webpack.config.js ... resolve: { modulesDirectories: ['node_modules', './src/component'] } ... |
1 2 3 4 5 6 7 8 9 10 |
//app.js //配置resolve.modulesDirectories前 import a form '../../src/component/a' import b form '../../src/component/b' //配置resolve.modulesDirectories后 import a form 'a' import b form 'b' |
具体使用参考官网 resolve 部分
plugins
插件,比 loader 更强大,能使用更多 webpack 的 api。webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。本文第七章将会介绍一些常用的插件。
1 2 3 4 5 6 7 8 9 10 11 12 |
var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { plugins: { new webpack.optimize.OccurenceOrderPlugin(), //为组件分配ID new webpack.HotModuleReplacementPlugin(), //热加载插件 new webpack.optimize.UglifyJsPlugin(), //压缩混淆js new webpack.NoErrorsPlugin(),//跳过编译时出错的代码并记录 new ExtractTextPlugin('[name]-[hash:5].min.css') //将css单独打包并重命名 } } |
具体使用参考官网内置 plugins 列表 list-of-plugins 部分,这里有所有内置插件的详细使用说明。
externals
当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中。例如 React、jQuery 等文件我们想使用 CDN 的引用,不想把他们打包进输出文件。就可以通过配置 externals 参数来配置:
1 2 3 4 5 6 7 |
module.exports = { externals: { jQuery: true } }; |
然后在页面里引入<script src="//cdn/jquery.min.js"></script>
这样 jQuery 就不用打包了,直接指向 windows.jQuery 就好
具体使用参考官网 externals 部分
其他
除以上 6 个常用配置属性外,webpack 的配置属性还有context
、cache
、 profile
等等,个人觉得并不常用,各位可以前往官网 configuration 详细查看。
常用 Loaders 介绍
babel-loader
babel-loader 用来编译下一代 JavaScript,比如 ES6、React 等,编译 ES6、React 的话还需要安装 babel-preset-es2015、babel-preset-react。
eslint-loader
配合 eslint 用来规范纠错 js,你可能同时还会需要 babel-eslint、eslint-plugin-react。
style-loader、css-loader、less-loader、sass-loader
处理 css、less、sass 文件
postcss-loader
用 postcss 来处理 CSS,最被常用的是其 autoprefixer 插件
resolve-url-loader
用来解析 css 里的 url()
声明里的文件的相对路径,使用 css-loader
时一般也必须要同时使用 resolve-url-loader
file-loader
用来处理文件名及路径,比如给文件添加 hash,返回文件相对路径等。
url-loader
与上面的 file-loader 类似,但是当文件小于设定的 limit 时可以处理成 Data Url(base 64)。
raw-loader
把文件内容作为字符串返回。例如var fileContent = require('raw!./file.txt')
,这里把./file.txt
的内容作为字符串返回。
imports-loader
用于向一个模块的作用域内注入变量。
举个栗子,比如我们需要使用bootstrap.js
,这个文件虽然依赖jQuery
但其内部没有require('jquery')
,这导致文件内的jQuery
对象不可识别,所以模块化调用bootstrap.js
时就会报错jQuery is not defined
`。使用 imports-loader 的话:
1 2 3 |
require('imports?jQuery=jquery!bootstrap/dist/js/bootstrap'); |
imports-loader 会在 bootstrap 的源码前面,注入如下代码:
1 2 3 4 |
/* IMPORTS FROM imports-loader */ var jQuery = require("jquery"); |
exports-loader
用于向一个模块中提供导出模块功能。功能与imports-loader
类似,但他是在文件的最后添加一行。
举个栗子,比如file.js
中没有调用export
导出模块,或者没有define
定义模块,因此无法模块化调用它。可以使用 exports-loader:
1 2 3 |
require("exports?file,parse=helpers.parse!./file.js"); |
他会在./file.js
文件的最后添加如下代码:
1 2 3 4 5 |
/* EXPORTS FROM exports-loader */ exports["file"] = (file); exports["parse"] = (helpers.parse); |
expose-loader
这个 loader 是将某个对象暴露成一个全局变量。
举个栗子,比如把jQuery
对象暴露成全局变量。这样,那些bootstrap.js
之类的文件就都能访问这个变量了。
1 2 3 4 5 6 7 |
module: { loaders: [ { test: require.resolve("jquery"), loader: "expose?$!expose?jQuery" }, ] } |
react-hot-loader
React 的网页热加载刷新 loader。同时需要配合使用 webpack-dev-server 。
常用插件介绍
具体的使用方法请在官网list-of-plugins查询,本文只简单介绍几个常用插件。
CommonsChunkPlugin
插件用法比较多,常用的是把一些不经常更改的公共组件合并压缩成一个 common 文件。有些类库如utils, bootstrap之类的可能被多个页面共享,最好是可以合并成一个js,而非每个js单独去引用。这样能够节省一些空间。
似乎只有在多入口文件时才能把公共文件提取出来,但一般模块化都建议单入口文件,所以个人觉得这个插件对我并没有什么卵用,有多入口需求的同学可以使用。
extract-text-webpack-plugin
第三方插件,将 CSS 打包成独立文件,而非内联。
HotModuleReplacementPlugin
代码热替换。
HtmlWebpackPlugin
生成 HTML 文件,配合 ExtractTextPlugin 可以加入打包后的 js 和 css。
NoErrorsPlugin
报错但不退出 webpack 进程。
OccurenceOrderPlugin
通过计算模块出现次数来分配模块。通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID。这个经常被使用可以较快地获得模块。这使得模块可以预读,建议这样可以减少总文件大小。
ProvidePlugin
定义一个共用的插件入口。
1 2 3 4 5 6 |
new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery" }) |
这样就可直接在文件中使用$
,无需再require('jQuery')
。
UglifyJsPlugin
js 代码压缩混淆 。
DedupePlugin
检测完全相同(以及几乎完全相同)的文件,并把它们从输出中移除。
DefinePlugin
主要用来定义全局的环境变量,以便我们在自己的程序中引用它。
webpack-visualizer-plugin
一个第三方插件,可以生成一个文件查看项目引用的所有模块的占比。
常用优化手段及技巧
区分开发及生产环境
前端开发环境通常分为两种:
- 开发环境: 需要日志输出,sourcemap ,错误报告等等
- 生产环境:需要做代码压缩,对文件名进行 hash 处理等等
为了区分我们可以创建两个文件分别进行不同环境下的配置:
- webpack.config.dev.js // 开发环境
- webpack.config.prod.js // 生产环境
同时 webpack 还提供了 DefinePlugin 插件来设置全局环境变量,后面会根据设置的不同环境变量决定是否打包压缩,还是启动dev server 或者是 prod server
1 2 3 4 5 6 7 |
plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') // or development }) ] |
判断当前环境是否是生产环境
1 2 3 4 5 |
var isProduction = function () { return process.env.NODE_ENV === 'production'; } |
使用代码热替换
使用代码热替换在开发的时候无需刷新页面即可看到更新,只在开发环境的配置文件使用,具体配置如下。
1 2 3 |
npm install --save-dev webpack-dev-server webpack-dev-middleware express |
把webpack/hot/dev-server
加入到 webpack 配置文件的 entry 项:
1 2 3 4 5 6 7 |
<!-- webpack.config.dev.js --> entry: [ 'webpack-dev-server/client?http://localhost:3000', './src/app.js' ], |
把new webpack.HotModuleReplacementPlugin()
加入到 webpack 配置文件的 plugins 项:
1 2 3 4 5 6 |
<!-- webpack.config.js --> plugins: [ new webpack.HotModuleReplacementPlugin() ] |
在项目根目录新建个server.js
文件,将server
部分分离到一个单独的 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<!-- server.js --> var webpack = require('webpack'); var WebpackDevServer = require('webpack-dev-server'); var config = require('./webpack.config'); var compiler = webpack(config); var server = new WebpackDevServer(compiler, { publicPath: config.output.publicPath, hot: true, historyApiFallback: true, stats: { colors: true, hash: false, timings: true, chunks: false, chunkModules: false, modules: false } }); server.listen(3000, 'localhost', function(err, result) { if (err) { return console.log(err); } console.log('Listening at http://localhost:3000/'); }); |
在 package.json 中定义启动监听热加载:
1 2 3 4 5 6 |
<!-- package.json --> "scripts": { "start": "node server.js" } |
现在你可以通过运行npm start
启动服务器。
编译 ES6、React
这里主要是借助 Babel 进行编译。
安装依赖:
1 2 3 |
npm install --save-dev babel-loader babel-core babel-preset-stage-0 babel-preset-es2015 babel-preset-react babel-preset-react-hmre |
在项目根目录新建个.babelrc
文件:
1 2 3 4 5 6 |
<!-- .babelrc --> { "presets": ["es2015", "stage-0", "react"] } |
配置 webpack 配置文件的 module 项:
1 2 3 4 5 6 7 8 9 10 |
<!-- webpack.config.js --> module: { loaders: [{ test: /\.js$/, loaders: ['react-hot', 'babel'], //若生产环境下去掉'react-hot' include: path.join(__dirname, 'src') }}] } |
使用 ESLint 检查规范 js 代码
ESLint 是个代码错误与风格检测工具,可以辅助编码规范执行,有效控制代码质量。
ESLint 主要有以下特点:
- 默认规则包含所有 JSLint、JSHint 中存在的规则,易迁移;
- 规则可配置性高:可设置「警告」、「错误」两个 error 等级,或者直接禁用;
- 包含代码风格检测的规则(可以丢掉 JSCS 了);
- 支持插件扩展、自定义规则。
安装依赖:
1 2 3 |
npm install --save-dev eslint eslint-loader eslint-plugin-react |
项目根目录新建.eslintrc
文件,用来配置 ESLint 规则,以下为常见规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!-- .eslintrc --> { "parserOptions": { //EsLint通过parserOptions,允许指定校验的ecma的版本,及ecma的一些特性 "ecmaVersion": 6, //指定ECMAScript支持的版本,6为ES6 "sourceType": "module", //指定来源的类型,有两种”script”或”module” "ecmaFeatures": { // ecmaFeatures指定你想使用哪些额外的语言特性 "jsx": true //启动JSX } }, "parser": "babel-eslint", // EsLint默认使用esprima做脚本解析,也可以切换成babel-eslint解析 "env": { // Environment可以预设好的其他环境的全局变量,如brower、node环境变量、es6环境变量、mocha环境变量等 "browser": true, "node": true, "es6": true, "mocha": true }, "plugins": [ // EsLint允许使用第三方插件 "react" ], extends: [ // Extends是EsLint默认推荐的验证你可以使用配置选择哪些校验是你所需要的 "eslint:recommended" ], rules: [ // 自定义规则 no-empty: ["error", { "allowEmptyCatch": true }], "strict": [2, "never"], "react/jsx-uses-react": "error", "react/jsx-uses-vars": "error", "no-console": ["error", { allow: ["warn", "error", "log"] }] ], "globals": { // 即插件在执行过程中用到的其它全局变量 } } |
在项目根目录新建.eslintignore
文件告诉 ESLint 去忽略特定的文件和目录:
1 2 3 4 5 6 7 8 9 |
<!-- .eslintignore --> dist node_modules dependent coverage webpack.*.js *Server.js |
在Sublime中安装插件:
SublimeLinter
SublimeLinter-contrib-eslint
更多请访问 eslint、babel-eslint、eslint-plugin-reac、eslint-loader、ESLint 使用入门、Lint Like It’s 2015
压缩代码
使用 webpack 内置的 UglifyJsPlugin 即可:
1 2 3 4 5 6 7 8 9 10 |
<!-- webpack.config.js --> plugins: [ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ] |
Code Spliiting,分割代码,按需加载
对于大型的web 应用而言,把所有的代码放到一个文件的做法效率很差,特别是在加载了一些只有在特定环境下才会使用到的阻塞的代码的时候。Webpack有个功能会把你的代码分离成Chunk,后者可以按需加载。这个功能就是 Code Spliiting
Code Spliting的具体做法就是一个分离点,在分离点中依赖的模块会被打包到一起,可以异步加载。一个分离点会产生一个打包文件。
本文这里主要是基于 React 与 React-Router 的 Code Spliiting。开篇说了 webpack 目前已经更新到 2.x 版本, 1.x 与 2.x 的 Code Spliiting 方法略有不同,这里分开来讲。
webpack 1.x 的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<Router history={history}> <Route path="/" getComponent={(location, callback) => { require.ensure([], function (require) { callback(null, require('./HomePage.jsx')); }); }} /> <Route path="/about" getComponent={(location, callback) => { require.ensure([], function (require) { callback(null, require('./AboutPage.jsx')); }); }} /> </Router> |
webpack 2.x 的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Page extends React.Component { render() { <Route path={this.props.path} getComponent={(location, callback) => { System.import(this.props.component) .then((component) => { callback(null, component); }); } } } |
1 2 3 4 5 |
<Router history={history}> <Page path="/about" component="./AboutPage.jsx" /> </Router> |
使用 DllPlugin 和 DllReferencePlugin 分割代码
通过 DllPlugin 和 DllReferencePlugin,webpack 引入了另外一种代码分割的方案。我们可以将常用的库文件打包到 dll 包中,然后在 webpack 配置中引用。业务代码的可以像往常一样使用 require 引入依赖模块,比如 require(‘react’), webpack 打包业务代码时会首先查找该模块是否已经包含在 dll 中了,只有 dll 中没有该模块时,webpack 才将其打包到业务 chunk 中。
首先我们使用 DllPlugin 将常用的库打包在一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var webpack = require('webpack'); module.exports = { entry: { vendor: ['lodash','react'], }, output: { filename: '[name].[chunkhash].js', path: 'build/', }, plugins: [new webpack.DllPlugin({ name: '[name]_lib', path: './[name]-manifest.json', })] }; |
该配置会产生两个文件,模块库文件:vender.[chunkhash].js 和模块映射文件:vender-menifest.json。其中 vender-menifest.json 标明了模块路径和模块 ID(由 webpack 产生)的映射关系,其文件内容如下:
1 2 3 4 5 6 7 8 9 10 11 |
{ "name": "vendor_lib", "content": { "./node_modules/.npminstall/lodash/4.17.2/lodash/lodash.js": 1, "./node_modules/.npminstall/webpack/1.13.3/webpack/buildin/module.js": 2, "./node_modules/.npminstall/react/15.3.2/react/react.js": 3, ... } } |
然后在业务代码的 webpack 配置文件中使用 DllReferencePlugin 插件引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var webpack = require('webpack'); module.exports = { entry: { app: ['./app'], }, output: { filename: '[name].[chunkhash].js', path: 'build/', }, plugins: [new webpack.DllReferencePlugin({ context: '.', manifest: require('./vendor-manifest.json'), })] }; |
需要注意的是:dll包的代码是不会执行的,需要在业务代码中通过require显示引入。
编译 less/sass、自动添加浏览器前缀、将 css 单独打包
我用的是 less,所以这里以 less 为例。
安装编译 less 的依赖:
1 2 3 |
$ npm install css-loader style-loader less less-loader --save-dev |
安装处理 css 内 url()
声明路径的依赖:
1 2 3 |
$ npm install resolve-url-loader --save-dev |
安装 autoprefixer 依赖:
1 2 3 |
$ npm install postcss-loader autoprefixer --save-dev |
配置 webpack 配置文件的 module、postcss项:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!-- webpack.config.js --> module: { loaders: [{ test: /\.less$/, loader: 'style', 'css?modules&importLoaders=1&localIdentName=[name]-[local]-[hash:base64:5]!resolve-url!postcss!less' include: path.join(__dirname, 'src') }}] }, postcss: [ require('autoprefixer') ] |
生产环境下我们可能会需要把 css 单独打包出来,这时需要用到 ExtractTextPlugin 插件 :
1 2 3 |
npm install extract-text-webpack-plugin --save-dev |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var ExtractTextPlugin = require('extract-text-webpack-plugin'); ... plugins: [ new ExtractTextPlugin('[name]-[hash:5].min.css') ], module: { loaders: [{ test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]-[local]-[hash:base64:5]!resolve-url!postcss!less') include: path.join(__dirname, 'src') }}] }, postcss: [ require('autoprefixer') ] ... |
压缩图片、将图片转为 base64
图片处理常见的loader有以下三种:
- file-loader: 默认情况下会根据图片生成对应的 MD5 hash 的文件格式
- url-loader: url-loader类似于file-loader,但是url-loader可以根据自定义文件大小或者转化为 base64 格式的 dataUrl,或者单独作为文件,也可以自定义对应的 hash 文件名
- image-webpack-loader: 提供压缩图片的功能
安装依赖:
1 2 3 |
npm url-loader image-webpack-loader --save-dev |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module: { loaders: [ { test: /\.(jpe?g|png|gif|svg)$/i, loaders: [ 'url?limit=10000&name=img/[hash:8].[name].[ext]', // 图片小于8k就转化为 base64, 或者单独作为文件 'image-webpack' // 图片压缩 ] } ] } |
给文件添加 hash 缓存、自动生成页面
在 output 项给生成文件添加 hash:
1 2 3 4 5 6 7 |
output: { ... filename: '[chunkhash:8].bundle.js' // chunkhash 默认是16位,可自定义配置 ... } |
文件名带上 hash 值后,这个值在每次编译的时候都会发生变化,都需要在 html 文件里手动修改引用的文件名,这种重复工作很琐碎且容易出错,这里我们可以使用 html-webpack-plugin 来帮我们自动处理这件事情:
1 2 3 4 5 6 7 8 9 10 11 12 |
// 在 app 目录下建一个 index.tpl.html 作为钩子 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> </head> <body> <div id="root"></div> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 在 webpack.config.dev.js 和 webpack.config.prod.js 添加配置代码,即可生成相对应的 index.html plugins: [ new HtmlWebpackPlugin({ template: 'app/index.tpl.html', minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, inject: 'body', filename: 'index.html' }) ] |
使用 Fetch
我个人的项目里 Fetch 已经完全替代 Ajax 了,使用 Fetch 为了兼容旧浏览器需要使用一些腻子脚本,我们可以将腻子脚本暴露到全局。这里主要使用前文中提到的import-loader、exports-loader。
安装依赖:
1 2 3 4 |
npm install imports-loader exports-loader --save-dev npm install es6-promise whatwg-fetch --save |
配置 webpack 配置文件的 plugins 项:
1 2 3 4 5 6 7 8 |
plugins: [ new webpack.ProvidePlugin({ 'Promise':'es6-promise', 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch' }), ], |
这样就可以直接在开发文件中使用 Fetch。
总结
再次说明下本文的 配套 DEMO 请访问 https://github.com/tumars/boilerplate-webpack-react-es6-cssModule 查看。
本文还会继续更新修改,欢迎各位交流指教。
嗯,这篇介绍webpack的文章,当得起“全面”二字
可以转载吗
可以的。