[翻译]Webpack解惑
本文译自Webpack — The Confusing Parts,原文需FQ。
Webpack现在是React应用程序标配的模块打包器,我估计Angular2和其他框架的用户也在大规模使用。我第一次看到Webpack配置文件的时候,感觉实在太陌生,太混乱了。在玩了一段时间后,我现在觉得是Webpack独特的语法和新的理念导致了初学阶段的复杂度。顺便说一下,也正是因为这些理念,才让Webpack如此受欢迎。
正因为Webpack入门容易让人困惑,所以我写了这篇文章,希望能够帮助其他人更轻松的入门,用起来更顺手。这是第一部分。
Webpack的核心理念
Webpack的两个核心理念是:
- 万物皆模块:就像JS文件可以当做模块,那么其他所有的文件(CSS,图片,HTML)都可以当做模块。这样,就可以require(“myJSfile.js”)或者require(“myCSSfile.css”)。这意味着,我们可以对模块再进行细分,分割成更小更容易管理的粒度,实现复用等等。
- 按需加载,异步加载:一般的模块打包器会打包所有的模块然后生成一个巨大的输出文件bundle.js。但是在许多实际应用的APP中,这个bundle.js可能会有10MB-15MB那么大,并且总是会加载!而Webpack有一些功能可以分割代码然后生成多个"bundle"文件并且可以在你需要的时候异步加载。
1.开发模式 VS 生产模式
首先要注意的事情是Webpack有太多的功能,有些只有开发模式下可以用,有些只有生产模式下可以用,还有一些开发模式和生产模式下都可以用。
一般情况下,大部分项目都会用到许多功能,所以通常有两个大型的Webpack配置文件。
执行打包的话可以像下面这样在package.json中写脚本:
“scripts”: { //npm run build 生产模式打包 “build”: “webpack --config webpack.config.prod.js”, //npm run dev 开发模式打包并且运行dev-server “dev”: “webpack-dev-server” }
2.Webpack CLI VS webpack-dev-server
需要强调的一点是Webpack这个模块打包器提供了两个接口:
- Webpack CLI工具-默认接口(作为Webpack的一部分自动安装)。
- webpack-dev-server工具-一个Node.js服务器(需要单独安装)。
Webpack CLI(适用于生产模式的构建)
此工具可以通过CLI或者配置文件(默认值:webpack.config.js)获取配置并传递给Webpack进行打包。
初学Webpack的时候可以使用CLI,大部分可能只会用来进行生产模式的构建。
用法:
//用法1: //全局安装 npm install webpack-dev-server --g //在终端中运行 $ webpack-dev-server --inline --hot //用法2: //写到package.json的脚本里 “scripts”: { “start”: “webpack-dev-server --inline --hot”, ... } //运行脚本 $ npm start //打开浏览器 http://localhost:8080
Webpack VS webpack-dev-server 选项
值得注意的是,有些选项,例如"inline"和"hot"是webpack-dev-server独有的选项,还有一些选项像是"hide-modules"是CLI独有的选项。
webpack-dev-server CLI 选项 VS config 选项
还有一点要注意的是,有两种方式向webpack-dev-server传递选项:
- 通过webpack.config.js里的"devServer"对象。
- 通过CLI选项。
//通过 CLI webpack-dev-server --hot --inline //通过 webpack.config.js devServer: { inline: true, hot:true }
我发现有时候通过devServer配置(hot: true和inline: true)不起作用,所以最好还是把命令写到package.json里,通过CLI传递选项。
//package.json { scripts: {“start”: “webpack-dev-server --hot --inline”} }
注意:确保没有同时传递hot: true和--hot。
“hot” VS “inline” webpack-dev-server 选项
"inline"选项为整个页面添加了"Live Reloading"功能,而"hot"选项开启了"Hot Module Reloading"功能,这样就会尝试着重载发生变化的组件,而不是整个页面。
//当源码改变时,下面三种命令都会导致重新打包,但是 //1. 不会重载浏览器中的页面 $ webpack-dev-server //2. 会重载整个页面 $ webpack-dev-server --inline //3. reloads just the module(HMR), or the entire page if HMR fails //3. 重载改变的模块(HMR),如果HMR失败的话就重载整个页面 $ webpack-dev-server --inline --hot
3."entry"-String VS Array VS Object
Entry的作用是告诉Webpack根模块,或者说起点在哪。值可以是String,Array或者Object。这里可能会让你困惑,但是不同的类型有各自的用武之地。
如果只有一个entry(大部分APP都是),可以选择任何格式,结果都是一样的。
entry-Array
如果你想添加多个文件并且这些文件不会互相依赖的话,可以使用Array格式。 例如:你可能想在HTML中放入"googleAnalytics.js",可以像下图这样把它添加到到bundle.js的结尾
entry-Object
现在,假设你有一个真正的多页应用,而不是一个多页面的SPA。有多个HTML文件(index.html和profile.html)。然后可以通过对象形式的entry来让Webpack一次性生成多个包。
下面的配置会生成两个JS文件,indexEntry.js和profileEntry.js,可以分别在indexEntry.html和profileEntry.html中使用。
//profile.html <script src=”dist/profileEntry.js”></script> //index.html <script src=”dist/indexEntry.js”></script>
注意:文件名来自"entry"对象的键名。
entry-组合
可以在entry对象中使用数组形式的entry,举个例子,下面的配置会生成三个文件:一个包含有三个库文件的vendoe.js,一个index.js和一个profile.js。
4.output — "path" Vs "publicPath"
output的作用是告诉Webpack如何存储生成的结果文件,其中path和publicPath这两个属性可能会让人困惑。
path仅仅用来告诉Webpack在哪里存放结果文件,而publicPath被一些Webpack插件用来处理CSS,HTML文件中的URL,一般用于生产模式。
例如,CSS里,可能会用./test.png这样的url来加载本地图片,但是在生产模式中,"test.png"这种图片可能是放在CDN上的,服务器是跑在云平台上的。这就意味着,生产模式中,你不得不手动处理文件里的URL保证它们指向的是CDN地址。
现在,你可以用Webpack的publicPath和一些具有publicPath检测功能的插件来自动处理文件中的URL。
// 开发模式:服务器和图片都在本地 .image { background-image: url(‘./test.png’); } // 生产模式:服务器跑在云平台上,图片在CDN上 .image { background-image: url(‘https://someCDN/test.png’); }
Loader 和 Loader链
Loader是用来加载(load)或输入(import)文件的Node模块,不同的loader可以将各种类型的文件转换为浏览器能够接受的格式如JS,Stylesheets等等。更进一步来说,loader允许通过require或ES6的import语句在JS文件中引入各种文件。
例如,可以用babel-loader把ES6语法写的JS转换成浏览器能够兼容的ES5形式:
module: { loaders: [{ test: /\.js$/, ←检测".js"文件, 通过的话,则使用对应loader exclude: /node_modules/, ←排除 node_modules 文件夹 loader: ‘babel’ ←使用 babel (‘babel-loader’的简写形式) }]
Loader链(从右向左工作)
多个loader可以链式调用,作用于同一种文件类型。工作链的调用顺序是从右向左,各个loader之间使用"!"分开。
例如,有一个CSS文件myCSSFile.css,我们想把文件里的内容放到HTML文件的标签中。想实现这一点,需要用到两个loader:css-loader和style-loader。
工作流程如下图:
- Webpack搜索模块内依赖的CSS文件,通过检查JS文件中是否有require(myCSSFile.css)语句。如果找到了这个依赖文件,Webpack首先把文件传给css-loader。
- css-loader载入所有的CSS语句,和CSS文件内的依赖(例如@import otherCSS),处理为JSON,接着Webpack会把这个JSON传给style-loader。
- style-loader拿到这个JSON之后会把它放到一个style标签中-,然后把这个style插入index.html文件中。
6.Loader本身也是可以配置的
Loader本身也是可以配置的,传入不同的参数可以实现不同的功能。
在下面的例子中,我们配置url-loader使小于1024字节的图片使用DataURL,大于1024字节的图片使用URL。如下图,有两种方式传入limit参数来实现此功能。
7. .babelrc文件
babel-loader用presets配置将ES6转换为ES5,把React JSX解析为JS。我们可以像下面这样传入query参数来配置:
module: { loaders: [ { test: /\.jsx?$/, exclude: /(node_modules|bower_components)/, loader: 'babel', query: { presets: ['react', 'es2015'] } } ] }
然而许多项目中babel的配置会很复杂,所以最好把所有的babel-loader配置写在.babelrc文件里。babel-loader会自动加载.babelrc文件如果该文件存在的话。 所以在许多例子中,你会看到下面这种写法:
//webpack.config.js module: { loaders: [ { test: /\.jsx?$/, exclude: /(node_modules|bower_components)/, loader: 'babel' } ] } //.bablerc { “presets”: [“react”, “es2015”] }
8.插件
插件是用来处理打包结果的额外模块。
例如,uglifyJSPlugin会对bundle.js进行压缩和混淆处理,可以压缩文件体积。
还有extract-text-webpack-plugin会在内部调用css-loader和style-loader把所有的CSS收集在一起,最后把结果抽取到一个单独的外部styles.css文件并且在index.html中链接styles.css。
//webpack.config.js //收集所有的.css文件,合并其中的内容并且抽取到一个单独的"styles.css" var ETP = require("extract-text-webpack-plugin"); module: { loaders: [ {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") } ] }, plugins: [ new ExtractTextPlugin("styles.css") //Extract to styles.css file ] }
注意:如果想要把CSS放到HTML的style标签中,可以不使用extract-text-webpack-plugin,只要用css-loader和style-loader就可以了。就像下面这样:
loaders: [{ test: /\.css$/, loader: ‘style!css’ <--(style-loader!css-loader的简写形式) }]
9.Loader VS 插件
你可能会意识到,Loader是在打包前或打包的过程中作用于单个文件。插件通常在打包过程结束后,作用于包或者chunk级别。还有一些插件例如commonChunksPlugins,更进一步,会修改如何创建包。
10.解析文件扩展名
许多Webpack配置文件都有一个resolve extensions属性,其中包含一个像那面那样的空字符串。这个空字符串是用来帮助解析没有扩展名的文件输入,例如:require('./myJSFile')或者import myJSFile from './myJSFile'。
{ resolve: { extensions: [‘’, ‘.js’, ‘.jsx’] } }
作者:松子
链接:https://zhuanlan.zhihu.com/p/24744677 来自知乎的分享