因为web应用扩展地得极其迅猛,前端技术也是突飞猛进,前端的苦不是有多难学,而是我刚学完,这东西就被淘汰了(手动哭脸)。框架方面咱们有vue、react、angular,咱们须要写vue单文件,也须要写jsx语法;js方面咱们有Typescript、es6(七、八、9),如今是每一年一个小版本的迭代,语法也是在不断更新和淘汰;模块方面咱们有es6的modlue、CommonJS、AMD;css方面咱们有sass、less、postcss。新技术层出不穷,浏览器实现跟不上,还要考虑到浏览器兼容性问题,是一个使人头大的问题。可是webpack能够轻松帮咱们解决这些问题,咱们能够在webpack的世界中放心地使用上述提到的技术,以更方便、舒服的方式完成咱们开发。css
At its core, webpack is a static module bundler for modern JavaScript applications.
这是官网的定义,webpack就是一个静态模块的打包工具。webpack能够将工程中的静态资源根据咱们声明的依赖关系,打包出最后的正常运行的输出文件。官网显示的这幅图很形象地描述了这个过程:html
在webpack中,全部的静态资源均可以被处理为一个模块,包括js、图片、css、字体。模块化是前端开发的主要模式,模块化的好处有不少,我想到的有如下3种:前端
在es6以前,原生js是不支持模块化开发。由于js设计之初,只是为了实现表单验证等简单的交互,并无考虑到要用js来写大型的web应用。随着js承担地职责愈来愈大,模块化开发的需求愈来愈急迫。因而,js社区诞生出了CommonJS、AMD这样的js模块化标准,随后在es6中,也终于加入了module的原生支持。如今最新版本的各大浏览器均已实现了对es6的module语法支持,但也仅限于最新的几个版本,详情能够查看一下can i ues。咱们能够把webpack当成是模块化标准的实现方案,但webpack的功能不只限于此。vue
webpack支持多种模块使用方式,包括es6的module、CommonJS、AMD。推荐使用es6的module语法,一方面是由于它是标准,是之后模块化语法的主要使用方式;另外一方面,是由于它是静态的,webpack能够依靠静态分析,作一些优化,好比Treeshaking。webpack自带js模块处理功能,其余类型的静态资源咱们须要经过配置相应的loader去处理。node
webpack中最重要的概念有如下几个:react
webpack通常根据配置文件去执行打包任务,咱们建立一个webpack.config.js文件来编写咱们的打包配置。jquery
Entry,顾名思义就是工程的入口文件,Entry的配置写法有三种:webpack
module.exports = { entry: { app: './src/app.js', vendors: './src/vendors.js' } };
module.exports = { entry: './path/to/my/entry/file.js' };
module.exports = { entry: ['./path/to/my/entry/file.js', './path/to/my/entry/file1.js'] };
入口通常用对象写法便可,其余两种写法的可忽略。git
Output用于配置打包输出的文件,包括输出文件的文件名、输出路径、静态资源地址,这里列出最经常使用的4种:es6
module.exports = { entry: { app: './src/app.js', search: './src/search.js' }, output: { filename: 'js/[name].js', chunkFilename: 'js/[name].js', path: __dirname + '/dist', publicPath: 'http://cdn.example.com/assets/[hash]/' } };
配置项以下:
filename: 配置输出文件名,可添加路径配置(例子中js/),可以使用占位符,占位符有如下5种:
?
后面的内容publicPath: 用于设置打包过程当中产生的静态文件的最终引用地址,静态文件的最终引用地址为output.publicPath + output.filename
,不少时候,你的静态文件放置在CDN上,经过publicPath就能够很方便地设置。若是你的静态引用地址在运行时才能肯定,能够在入口文件中设置__webpack_public_path__
来决定publicPath的值:
__webpack_public_path__ = myRuntimePublicPath; // rest of your application entry
CommonsChunkPlugin
中产生,这是一个Plugin,用于抽取公共代码或者进行代码分割等操做,该plugin已经在webpack4中废除,由webpac4内置的optimization.splitChunks
替代,后面会讲到output还有其余不少配置,这4个是经常使用配置。
Loaders能够理解为不一样类型模块的处理器,将这些类型的模块处理为浏览器可运行和识别的代码。好比babel-loader将es6以上代码转换为es5代码;sass-loader将sass代码解析为css代码;url-loader和file-loader能够将图片、字体等静态文件解析为base64码或者静态文件地址。Loaders给咱们提供了处理模块的入口,在里面可使用所有的js功能,从而使webpack具备了强大而灵活的能力。webpack及webpack社区提供了功能强大的loader供开发者使用,你也能够本身编写loader。下面介绍一下在工程中经常使用的loader。
使用babel将ES2015+的代码转码为ES5的代码,babel的具体配置可参考babel官网
modlue: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: 'babel-loader } ] }
exclude表示不处理的目录,通常node_modules
中的第三方js文件不在咱们的处理范围内。
该loader使对应的js文件在全局环境中运行一次。好比咱们在工程中使用了jquery的插件,须要全局暴露$,咱们就须要让jquery文件在全局环境中运行,以便让它把$挂载到全局变量中,效果和在浏览器中加script标签同样。
import 'jquery'; module: { rules: [ { test: /jquery$/, use: [ 'script-loader' ] } ] }
解析一个sass文件,并不仅须要一个loader,它须要多个loader串行处理,webpack能够配置多个loader串行处理:
module: { rules: [ { test: /\.sass$/, use: [ 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader' ] } ] }
咱们能够将use配置为一个数组,loader从右往左依次执行,且前一个loader的结果是下一个loader的输入。最后一个loader的输出就是咱们最终要的结果。一个sass文件首先通过sass-loader
处理,变成css文件,又通过postcss-loader
处理,添加浏览器前缀等功能,接着交给css-loader
去解析css文件引用的静态变量,最后由style-loader
以script
标签的形式加入到html中。
url-loader
和file-loader
是一对用来处理图片、svg、视频、字体等静态资源文件的loader。通常体积比较小的资源交给url-loader
处理,编码为base64字符串,直接嵌入js文件中。体积较大的文件由file-loader处理,直接释放为了一个输出文件。
{ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: 'img/[name].[hash:7].[ext]' } },
通常只配置url-loader
便可,在资源超过limit的时候,url-loader
会将资源自动交给file-loader
处理,并将options内容也传递给file-loader。
loaders用来转换某种特定类型的module,plugins则用来在一些合适的时机执行一些特定的任务,好比代码分割、静态资源处理、环境变量的注入、将全部css的module抽取为单个文件等。webpack自身也是用插件系统构建起来的。插件的目的是作任何loaders作不了的事情。
HtmlWebpackPlugin插件能够用来生成包含你全部打包文件(js和css)的html文件,特别是你在打包文件名配置了hash,就不得不用这个插件了。
module.exports = { plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'src/index.html'), filename: 'static/index.[hash].html', inject: true, minify: false, chunks: ['app', 'vendor'] }) ] };
MiniCssExtractPlugin
将一个chunk中的css抽取为一个单独的css文件,若是chunk中不包含css,则不生成文件。且支持按需加载和sourceMap。webpack4新增插件,在webpack4以前是使用ExtractTextWebpackPlugin
来作这件事。官方文档总结了MiniCssExtractPlugin
相对ExtractTextWebpackPlugin
的四个优点:
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const devMode = process.env.NODE_ENV !== 'production' module.exports = { plugins: [ new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: "[name].css", chunkFilename: "[id].css" }) ], module: { rules: [ { test: /\.css$/, use: [ devMode ? 'style-loader' : { // MiniCssExtractPlugin目前尚未HMR功能,因此最好只在生成环境使用 loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' // 可单独配置publicPath,默认采用output中的publicPath } }, "css-loader" ] } ] } }
CopyWebpackPlugin用来处理静态文件,能够将文件或者文件夹原封不动地移动到打包目录。
const CopyWebpackPlugin = require('copy-webpack-plugin') const config = { plugins: [ new CopyWebpackPlugin([ { from: 'source', to: 'dest' }, { from: 'source', to: 'dest', toType: 'dir|file|template' }, // 手动设置to的类型,好比设置成dir,即便to设置为a.js,最后也会生成a.js文件夹 { from: 'source', to: 'dest', context: '/app' }, // 基准目录,from相对于context解析 { from: 'source', to: 'dest', ignore: ['*.js'] }, // ignore, 忽略匹配的文件 'source' // 只有from,to默认为output的path ], { context: '/app', // 同上 ignore: ['*.js'], // 同上 }) ] }
CleanWebpackPlugin用来清除打包目录,主要用于每次从新生成的时候,清除残留文件。在文件有hash值的状况下,是必要的。
const CleanWebpackPlugin = require('clean-webpack-plugin'); const config = { plugins: [ new CleanWebpackPlugin(['dist', 'bulid/*.js'], { watch: false, // 是否在--watch模式下也清除files而后从新编译,默认为false exclude: [ 'files', 'svg', 'css' ] // 不删除的子目录和文件 }), ] }
DefinePlugin用来定义webpack编译期间的全局变量。咱们能够根据这些变量,来作不一样的动做。最典型的就是能够区分开发环境和生产环境,好比在开发环境打印各类警告、错误,在生产环境去掉这些跟业务无关的代码。
DefinePlugin的参数是一个对象,键名是一个标识符,或者用.
隔开的多级标识符,参数遵循如下规则
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production'), PRODUCTION: JSON.stringify(true), VERSION: JSON.stringify('5fa3b9'), BROWSER_SUPPORTS_HTML5: true, TWO: '1+1', 'typeof window': JSON.stringify('object') });
index.js
console.log('PRODUCTION', PRODUCTION); console.log('VERSION', VERSION); console.log('BROWSER_SUPPORTS_HTML5', BROWSER_SUPPORTS_HTML5); console.log('TWO', TWO); console.log('Object', typeof window);
被编译为
console.log('PRODUCTION', true); console.log('VERSION', "5fa3b9"); console.log('BROWSER_SUPPORTS_HTML5', true); console.log('TWO', 1+1); console.log('Object', false ? undefined : _typeof(window)); // 不太明白
DefinePlugin的原理很简单,只是在编译过程当中遇到这些定义好的键名,就用键值作简单的文本替换。因此,你若是想给全局变量赋一个字符串,须要这样写'"production"'
,通常使用JSON.stringify
来转一下。
webpack4新增了mode
配置。webpack会根据mode
值自动帮你作一个不一样的优化:
production(默认值)
DefinePlugin
中将process.env.NODE_ENV
设置为production
FlagDependencyUsagePlugin
, FlagIncludedChunksPlugin
, ModuleConcatenationPlugin
, NoEmitOnErrorsPlugin
, OccurrenceOrderPlugin
, SideEffectsFlagPlugin
and UglifyJsPlugin
development:
DefinePlugin
中将process.env.NODE_ENV
设置为development
NamedChunksPlugin
, NamedModulesPlugin
mode
的两种使用方式
module.exports = { mode: 'production' };
webpack --mode=production
代码分割能够把代码按照必定的逻辑分割,用来作按需加载或者并行加载,以减小加载时间。在webpack中,主要有如下3中方式,实现代码分割:
entry
入口配置处配置多个入口import()
语法,webpack使用SplitChunks
将import()
加载的module分割为一个单独的chunk,并在函数执行时加载该chunk对应的js文件。第一种没什么好说的,主要说一下后两种。
在webpack4以前的版本,都是使用CommonsChunkPlugin
来抽取公共chunk,在webpack4中废弃了CommonsChunkPlugin
,转而支持内置的optimization.splitChunks
。
splitChunks
默认只对按需加载的chunk起做用,会自动将import()引入的module分割为单独chunk,可是须要知足如下条件:
node_modules
在执行后两个原则的时候,体积大的chunk被优先生成。
splitChunks
的默认配置以下,咱们能够看出正好是和上面4个条件对应的:
module.exports = { optimization: { splitChunks: { chunks: 'async', minSize: 30000, // chunk只有超过这个大小才会被分割 maxSize: 0, // 大于这个体积的chunk会被自动分割为更小的chunk minChunks: 1, // 一个模块被共享的chunk数量大于minChunks时,才会被分割出来 maxAsyncRequests: 5, // 按需加载最大的并行数 maxInitialRequests: 3, // 初始加载最大的并行数 automaticNameDelimiter: '~', // name为true时,新chunk的文件名由cacheGroups的key加上chunks属性的一些信息生成,automaticNameDelimiter是分隔符 name: true, cacheGroups: { // 配置拆分规则,会继承splitChunks全部的配置项,全部splitChunks配置项均可以在这里重写覆盖,test、prioprity、reuseExistingChunk是cacheGroups独有的属性 vendors: { test: /[\\/]node_modules[\\/]/, // 模块匹配规则,能够是正则表达式或者函数,不写默认选择全部模块 priority: -10 // 优先级,当同一个模块同时包含在不一样cacheGroup中,该模块将被划分到优先级高的组中 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true // 若是该chunk包含的modules都已经另外一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再从新产生一个 } } } } };
splitChunks
的chunks属性表示做用的chunk范围。chunks能够是一个函数,彻底由开发者控制;也能够是一个字符串,字符串一共有3个值:
name属性表示最后生成chunk的文件名,有如下3中类型取值:
true
,则会根据cacheGroup的key和chunks属性的信息自动生成splitChunks
下,那全部chunk设置为相同的名称,这会形成不一样的chunk合成为一个chunk,固然你能够设置在cacheGroup
下Tree Shaking
此次词很形象,摇树,把烂掉的树叶摇下来。咱们工程中的烂树叶
就是那些咱们导出了,可是没有用到的代码,Tree Shaking
能够帮助咱们去除无效代码,减少打包体积,有其在大工程中,效果明显。
Tree Shaking
的使用三部曲:
使用Tree Shaking
的第一个前提条件就是必须使用ES2015的模块语法,import
,export
。由于ES2015的模块语法是静态加载
的,而CommonJS和AMD都是动态加载
。静态加载
是指在编译阶段你就能肯定导出和加载的内容。ES2015在语法上规定:你只能在顶层模块做用域进行导入导出操做,不容许在条件语句中导入导出,也不容许导入和导出的内容有变量。这意味着你只分析源码就能够肯定导入和导出的内容,而不是等到运行时才能肯定。好比说下面CommonJS
的语法,在ES6中就是不能使用的:
var my_lib; if (Math.random()) { my_lib = require('foo'); } else { my_lib = require('bar'); }
你只有在运行的时候,才知道到底加载是的foo
仍是bar
。ES6强制模块语法静态化,失去了必定的灵活性,可是带来了更多的好处,其中之一就是咱们能够经过静态分析去实现Tree Shaking。
在彻底的Es6模块世界中,代码是没有反作用的,可是如今咱们可能用到的各类地方库会有反作用。反作用
是指代码除了导入和导出,还作了一些其余影响了其余代码的行为,好比定义了全局变量。最典型的例子就是polyfills,好比Promise的polyfills就定义了Promise全局变量。这时候若是咱们分析到Promise有未使用的导出代码,则不能删除,不然可能会影响Promise的使用。哪些是有反作用
的代码,须要你识别,而且告诉webpack,方式就是经过设置sideEffects,能够设置在package.json文件中,也能够设置的module.rules中。
{ "name": "your-project", "sideEffects": [ "./src/some-side-effectful-file.js", "*.css" ] }
webpack会经过静态分析找到冗余代码,并打上标记,咱们看一下官网例子:
math.js
export function square(x) { return x * x; } export function cube(x) { return x * x * x; }
index.js
import { cube } from './math.js'; function component() { var element = document.createElement('pre'); element.innerHTML = [ 'Hello webpack!', '5 cubed is equal to ' + cube(5) ].join('\n\n'); return element; } document.body.appendChild(component());
在development
模式下打包,内容以下:
/* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { 'use strict'; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__['a'] = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; } });
咱们能够看到代码中并未用到math.js
中的square
方法,webpack输出文件中表示它未被使用。若是想去掉未被使用的代码,则须要用到UglifyJSPlugin
插件,它是用来压缩js文件的,自动启用了去除冗余代码的功能。咱们能够在production
模式下打包,会发现square
的代码已经被去除。