We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
一直对webpack的打包流程很感兴趣,但是无奈官网文档实在太多,搜出来的大部分文章要么偏理论要么纯粹讲过程不讲原理,最近终于找到一篇入门文章,文章对于初学者讲的很清晰,但是由于是英文的,而且我没有找到这篇文章对应的中文翻译版,所以本文主要是对那篇文章进行翻译,介绍一下webpack2的入门知识。 注:本人翻译水平有限,如果有错误,欢迎指正。 原文地址:A Beginner’s Guide to Webpack 2 and Module Bundling 原文作者:Mark Brown 译文作者:Allen Gong
Webpack是一个模块打包机
Webpack已然成为当前web开发最重要的工具之一。首先它是一个Javascript的打包工具,但同时他也能打包包括HTML,CSS,甚至是图片等形式的资源。它能更好的控制你正在编写的App的HTTP请求,并且允许你去使用更多的资源(如Jade,Sass以及ES6)。Webpack同时允许你更容易的从npm获取安装包。
这篇文章主要面向那些对于webpack完全陌生的同学,内容将包括初始安装和配置,模块,模块加载器,插件,代码拆分以及模块热替换(HMR,hot module replacement)。如果你觉得入门视频比较有用的话,我推荐Glen Maddern的Webpack初体验作为开始学习的起点,会让你理解为什么webpack如此特殊。
为了更加后续的阅读,请确保先安装了Node.js,安装可以参考Node.js安装教程。你也在Github上下载到对应的Demo。
让我们用npm和webpack新建一个项目吧:
mkdir webpack-demo cd webpack-demo npm init -y npm install webpack@beta --save-dev mkdir src touch index.html src/app.js webpack.config.js
编辑以下文件:
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello webpack</title> </head> <body> <div id="root"></div> <script src="dist/bundle.js"></script> </body> </html>
// src/app.js const root = document.querySelector('#root') root.innerHTML = `<p>Hello webpack.</p>`
// webpack.config.js const webpack = require('webpack') const path = require('path') const config = { context: path.resolve(__dirname, 'src'), entry: './app.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: [{ test: /\.js$/, include: path.resolve(__dirname, 'src'), use: [{ loader: 'babel-loader', options: { presets: [ ['es2015', { modules: false }] ] } }] }] } } module.exports = config
以上的设置只是通用配置,它会指导你的webpack将我们的入口文件src/app.js编译输入为/dist/bundle.js,并且所有的.js文件都将通过Babel从ES2015转换为ES5。
src/app.js
/dist/bundle.js
.js
为了让这个项目能运行起来,我们需要安装三个安装包,babel-core,webpack的加载器babel-loader以及预处理模块babel-preset-es2015,这些模块都是为了支持Javascript的编写。{ modules: false }可以确保使用Tree Shaking去去除掉不必要的模块,同时会降低文件大小。
babel-core
babel-loader
babel-preset-es2015
{ modules: false }
npm install babel-core babel-loader babel-preset-es2015 --save-dev
最后使用下面代码更新package.json:
package.json
"scripts": { "start": "webpack --watch", "build": "webpack -p" },
运行npm start将会以观察模式启动webpack,在这种模式下,会持续监听我们src文件夹下的.js文件。控制台的输出结果显示了生成的打包后的文件,我们应该持续关注生成的文件的大小和数量。
npm start
src
现在你可以在浏览器中访问index.html,将会看到“Hello webpack.”
index.html
open index.html
打开dist/bundle.js看看webpack到底做了什么事,在文件的顶部是bootstrapping模块的代码,在它下面是我们自己的模块。你可能目前还没有什么感觉webpack好处,但是你现在可以编写ES6代码并且webpack将会把各个模块打成生产所需要的包,这样所有浏览器都能访问。
dist/bundle.js
使用Ctrl + C停止webpack的服务,运行npm run build,编译成生成环境所需要的包。
Ctrl + C
npm run build
注意:包的大小从2.61 kB降到了585 bytes 重新看看dist/bundle.js,你会发现代码变得一团糟,UglifyJS对打包后的代码进行了压缩,运行起来是没有差别的,但同时字符数是相当少的。
对于外部模块,webpack有多种方式去引入,其中比较重要的两种是:
import
require()
我们可以通过安装lodash来测试上述方式,并且导入到app.js中。
app.js
npm install lodash --save
// src/app.js import {groupBy} from 'lodash/collection' const people = [{ manager: 'Jen', name: 'Bob' }, { manager: 'Jen', name: 'Sue' }, { manager: 'Bob', name: 'Shirley' }, { manager: 'Bob', name: 'Terrence' }] const managerGroups = groupBy(people, 'manager') const root = document.querySelector('#root') root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
运行npm start重启webpack并刷新index.html,你会在页面上看到一个按照manager分好组人名的数组。 接下来让我们把这个数组部分单独放在people.js这个模块里。
people.js
// src/people.js const people = [{ manager: 'Jen', name: 'Bob' }, { manager: 'Jen', name: 'Sue' }, { manager: 'Bob', name: 'Shirley' }, { manager: 'Bob', name: 'Terrence' }] export default people
我们可以以相对路径的方式将模块导入到app.js
// src/app.js import {groupBy} from 'lodash/collection' import people from './people' const managerGroups = groupBy(people, 'manager') const root = document.querySelector('#root') root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
注意:导入像'lodash/collection这种不使用相对路径的,是那些通过npm安装的,从/node_modules中引入,你自定义的模块则需要像'./people'相对路径的方式引入,通过这种方式可以对两种模块进行区分。
'lodash/collection
/node_modules
'./people'
我们已经介绍了babel-loader,它是众多loader中的一种,能够告诉webpack当遇到不同的文件时如何处理。比较好的方式是将loader进行串联,加载到一个加载器中,我们通过从Javascript中引入Sass包来看看loader是如何进行工作的。
这个转换器包括了三个单独的加载器和node-sass库:
node-sass
npm install css-loader style-loader sass-loader node-sass --save-dev
在配置文件中为.scss引入新的规则:
.scss
// webpack.config.js rules: [{ test: /\.scss$/, use: [ 'style-loader', 'css-loader', 'sass-loader' ] }, { // ... }]
注意:不管什么时候你改变了webpack.config.js中的加载规则,你都需要通过Ctrl + C然后npm start的方式重启webpack。
webpack.config.js
loader以倒序的方式运行:
sass-loader
css-loader
style-loader
<tag>
你可以将上述过程想象成函数的调用关系,一个函数运行的结果作为另一个函数的输入:
styleLoader(cssLoader(sassLoader('source')))
接下来让我们增加一个Sass源文件:
/* src/style.scss */ $bluegrey: #2B3A42; pre { padding: 20px; background: $bluegrey; color: #dedede; text-shadow: 0 1px 1px rgba(#000, .5); }
现在你可以在你的app.js中直接引入Sass文件:
// src/app.js import './style.scss' // ...
刷新index.html你会看到样式发生了变化。
我们刚刚把Sass作为一个模块引入到我们的入口文件中。
打开dist/bundle.js,搜索pre {。事实上,Sass已经被编译成一段CSS的字符串,并以模块的形式存在。当我们在我们的Javascript文件中导入这个模块时,style-loader就会将其编译输出成内嵌的<style>标签。
pre {
<style>
我知道你在想什么?为什么要这么做?
关于这个问题,我在这个话题中不想说太多,但下面几个原因值得思考一下:
最后一个关于loader的例子是关于处理图片的url-loader。 在标准HTML文档中,图片通过<img>标签或者background-image属性获得。但是通过webpack,一些小图片可以以字符串的形式存储在Javascript中。通过这种方式,你可以在预加载的时候就获取到图片,从而不需要单独的请求去请求图片。
url-loader
<img>
background-image
npm install file-loader url-loader --save-dev
在配置文件中增加一条图片的规则:
// webpack.config.js rules: [{ test: /\.(png|jpg)$/, use: [{ loader: 'url-loader', options: { limit: 10000 } // Convert images < 10k to base64 strings }] }, { // ... }]
通过Ctrl + C和npm start重启服务。 通过下面的命令下载一个测试图片:
curl https://raw.githubusercontent.com/sitepoint-editors/webpack-demo/master/src/code.png --output src/code.png
现在可以在app.js中加载图片资源:
// src/app.js import codeURL from './code.png' const img = document.createElement('img') img.src = codeURL img.style.backgroundColor = "#2B3A42" img.style.padding = "20px" img.width = 32 document.body.appendChild(img) // ...
这样页面中多了一个img,它的src属性包含了图片自身的data URI。
<img src="data:image/png;base64,iVBO..." style="background: #2B3A42; padding: 20px" width="32">
同时,因为css-loader的缘故,通过url()属性引入的图片,也通过url-loader转换成行内元素。
url()
/* src/style.scss */ pre { background: $bluegrey url('code.png') no-repeat center center / 32px 32px; }
编译后变成:
pre { background: #2b3a42 url("data:image/png;base64,iVBO...") no-repeat scroll center center / 32px 32px; }
现在你可以webpack是如何帮助你对将你项目中一系列的依赖资源进行打包处理的,下面这张图是webpack官网主页上的。
虽然Javascript是入口文件,但是webpack还是倾向于你的其他类型的资源像HTML, CSS, and SVG能有自己的依赖,把它们作为构建包的一部分。
我们已经看过了webpack其中一个构建插件的例子,使用UglifyJsPlugin的npm run build脚本可以调用webpack -p,它的作用是与webpack搭配压缩生成后的包。 当loader在单个文件上操作相应变换时,插件可以在各个大型代码块上交叉运行。
webpack -p
commons-chunk-plugin是另一个核心插件,搭配webpack用来创建在多个入口文件中使用的拥有公共代码的单文件模块。到目前为止,我们使用的都是单一入口和单一出口文件。但是很多real-world scenarios中更好的方法是使用多文件入口和多文件出口。 如果你在你的应用中有两个完全独立的领域但是却拥有共同的模块,举个例子,app.js是面向用户的,admin.js是面向管理员的,你就可以为他们单独创建不同的入口文件,就像下面这样:
admin.js
// webpack.config.js const webpack = require('webpack') const path = require('path') const extractCommons = new webpack.optimize.CommonsChunkPlugin({ name: 'commons', filename: 'commons.js' }) const config = { context: path.resolve(__dirname, 'src'), entry: { app: './app.js', admin: './admin.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].bundle.js' }, module: { // ... }, plugins: [ extractCommons ] } module.exports = config
注意对于结果文件,现在包含了名字,这样我们区分出两个不同的结果文件对应不同的入口文件:app.bundle.js和admin.bundle.js。
app.bundle.js
admin.bundle.js
commonschunk插件生成了第三个文件commons.js,他包含了我们入口文件的公共模块。
commonschunk
commons.js
// src/app.js import './style.scss' import {groupBy} from 'lodash/collection' import people from './people' const managerGroups = groupBy(people, 'manager') const root = document.querySelector('#root') root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
// src/admin.js import people from './people' const root = document.querySelector('#root') root.innerHTML = `<p>There are ${people.length} people.</p>`
这些入口文件将会产生下列文件:
lodash/collection
people
我们可以在两个入口文件中都引入公共模块:
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello webpack</title> </head> <body> <div id="root"></div> <script src="dist/commons.js"></script> <script src="dist/app.bundle.js"></script> </body> </html>
<!-- admin.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello webpack</title> </head> <body> <div id="root"></div> <script src="dist/commons.js"></script> <script src="dist/admin.bundle.js"></script> </body> </html>
试试在浏览器中重新加载index.html和admin.html,看看自动生成的公共模块部分。
admin.html
另一个受欢迎的插件是extract-text-webpack-plugin,它的用途是抽取模块到对应的结果文件中。 下面我们在配置文件中修改.scss的规则编译成对应的Sass文件,加载CSS,接着把他们抽取到各自的CSS包中,这样就可以把它们从Javascript包中移除。
npm install [email protected] --save-dev
// webpack.config.js const ExtractTextPlugin = require('extract-text-webpack-plugin') const extractCSS = new ExtractTextPlugin('[name].bundle.css') const config = { // ... module: { rules: [{ test: /\.scss$/, loader: extractCSS.extract(['css-loader','sass-loader']) }, { // ... }] }, plugins: [ extractCSS, // ... ] }
重启webpack你会看到一个新的打包后的文件app.bundle.css,你可以照例直接引用它。
app.bundle.css
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello webpack</title> <link rel="stylesheet" href="dist/app.bundle.css"> </head> <body> <div id="root"></div> <script src="dist/commons.js"></script> <script src="dist/app.bundle.js"></script> </body> </html>
刷新页面,确认CSS已经被编译过了,并从app.bundle.js移到了app.bundle.css,成功了!
我们已经看了几种代码拆分的方法:
拆分包还有其他方法:System.import和require.ensure。通过在这些函数中包含代码段,你可以创建一个在运行时按需加载的模块。这个从根本上提高了性能,因为在启动过程中不需要把所有东西都发送到客户端。System.import将模块名作为参数,并返回一个Promise对象。require.ensure获取依赖关系的列表,回调函数以及可选的模块名。 如果应用程序的某一部分具有很大的依赖关系,则应用程序的其余部分就不需要了,最好的方式就是拆分到各个模块中去。我们通过新建一个需要依赖d3的模块dashboard.js来证明这点。
System.import
require.ensure
dashboard.js
npm install d3 --save
// src/dashboard.js import * as d3 from 'd3' console.log('Loaded!', d3) export const draw = () => { console.log('Draw!') }
在app.js的顶部引入dashboard.js:
// ... const routes = { dashboard: () => { System.import('./dashboard').then((dashboard) => { dashboard.draw() }).catch((err) => { console.log("Chunk loading failed") }) } } // demo async loading with a timeout setTimeout(routes.dashboard, 1000)
因为我们加载了异步模块,我们需要在配置文件中增加output.publicPath属性,因此webpack知道去哪里获取。
output.publicPath
// webpack.config.js const config = { // ... output: { path: path.resolve(__dirname, 'dist'), publicPath: '/dist/', filename: '[name].bundle.js' }, // ... }
运行npm build操作,你会看到一个新的神秘的打包文件0.bundle.js。
npm build
0.bundle.js
注意webpack为了保持诚实,通过凸现[big]的包来让你保持关注。 这个0.bundle.js将会通过JSONP的请求按需加载,所以从文件目录中获取将不在有效,我们需要启动一个服务来获取文件。
[big]
python -m SimpleHTTPServer 8001
打开浏览器,输入http://localhost:8001/ 加载一秒钟后,你会获得一个GET请求,我们动态生成了/dist/0.bundle.js文件,在控制台上打印除了"Loaded!",成功!
/dist/0.bundle.js
当文件改变时,实时地重新加载能提高开发者的开发效率。只要安装它,并且以webpack-dev-server的形式启动,就可以体验啦。
webpack-dev-server
修改package.json中的start脚本:
start
"start": "webpack-dev-server --inline",
重新运行npm start,在浏览器中打开http://localhost:8080 试着去改变src目录中任何文件,如改变people.js中的任意一个名字,或者style.scss中的任意样式,去看看它如何实时改变。
style.scss
如果你对实时重新加载印象深刻,那么hot module replacement(HMR)一定会让你吃惊不已。
现在是2017年了,你在工作中已经可以在单页面应用中使用全局状态了。在开发过程中,你可能会对组件进行许多小的修改,并且希望能在浏览器中看到修改后生成的结果,这样可以实时去更改。但是通过刷新页面或者实时热更新并不能改变全局的状态,你就必须重头开始。但是HMR永远地改变了这一问题。
最后对package.json中的start脚本做修改:
"start": "webpack-dev-server --inline --hot",
在app.js中告诉webpack去接受这个模块以及对应依赖的热更新。
if (module.hot) { module.hot.accept() } // ...
注意:webpack-dev-server --hot设置了 module.hot为true,但只是在开发过程中。当以生产模式打包时,module.hot被设成了false,这样这些包就被从结果中抽离了。
webpack-dev-server --hot
module.hot
true
false
在webpack.config.js中增加一个NamedModulesPlugin插件,去改善控制台的记录功能。
NamedModulesPlugin
plugins: [ new webpack.NamedModulesPlugin(), // ... ]
最后我们在页面中增加一个<input>元素,我们可以在里面增加一些文字,用来确保我们更改自己模块时页面不会刷新。
<input>
<body> <input /> <div id="root"></div> ...
运行npm start重启服务,观察热更新如何工作吧。
为了实验,在input框中输入“HMR Rules”,接着改变一个people.js中的名字,你会发现页面在不刷新也能做出修改,而忽略input的状态。
这只是一个简单的例子,但是希望你能看到其广泛的用途。在诸如React的开发模式中,你可能有很多"哑巴"组件是与他们的状态分离开的,通过热更新,这些组件将不会失去状态,也能实时更新,因此你将获得及时的反馈。
修改style.scss文件中<pre>元素的背景颜色,你发现他并没有被HMR替换。
<pre>
pre { background: red; }
事实证明当你使用style-loader时,CSS的热更新将会免费为你提供而不需要你做任何特殊处理。我们只需要断开CSS模块与最终抽取的包之间的链接,这个包是无法被替换的。
如果我们将Sass规则恢复到原始状态,并从插件列表中删除extractCSS,那么您也可以看到Sass的热重新加载。
{ test: /\.scss$/, loader: ['style-loader', 'css-loader','sass-loader'] }
使用像webpack这样的模块打包工具的主要好处之一是,您可以通过控制资源的构建方式以及在客户端上的获取方式,从而帮助你提高性能。多年以来,它被认为是最佳实践,通过连接文件减少客户端请求。现在还是有效,但是HTTP2在单一请求中发送多文件,因此连接文件的方式不不再是"银弹"。你的应用程序实际上可以从多个小文件单独缓存,但客户端可以获取单个更改的模块,而不必再次获取大部分相同内容的整个包。
Webpack的创始人Tobias Koppers的撰写了一篇内容丰富的帖子,解释了为什么打包仍然很重要,即使在HTTP/2时代。
想了解更多请参考webpack & HTTP/2
我真心希望你已经发现这个介绍webpack 2的文章对你有帮助,并能够开始很好使用它。围绕webpack的配置,加载程序和插件可能需要一些时间,但是了解这个工具的工作原理后会对你有很大帮助。
文档仍在进行更新中,但如果您想将现有的Webpack1项目移到Webpack2,则可以参考Migrating from v1 to v2。
webpack是否是你打包的选择,从评论中你就可以知晓。
本文由Scott Molinari,Joan Yin和Joyce Echessa进行了同行评审。 感谢SitePoint的同行评议人员,使SitePoint内容成为最棒的内容!
本文翻译自A Beginner’s Guide to Webpack 2 and Module Bundling 翻译者:Allen Gong
The text was updated successfully, but these errors were encountered:
No branches or pull requests
背景
一直对webpack的打包流程很感兴趣,但是无奈官网文档实在太多,搜出来的大部分文章要么偏理论要么纯粹讲过程不讲原理,最近终于找到一篇入门文章,文章对于初学者讲的很清晰,但是由于是英文的,而且我没有找到这篇文章对应的中文翻译版,所以本文主要是对那篇文章进行翻译,介绍一下webpack2的入门知识。
注:本人翻译水平有限,如果有错误,欢迎指正。
原文地址:A Beginner’s Guide to Webpack 2 and Module Bundling
原文作者:Mark Brown
译文作者:Allen Gong
webpack2入门手册(译文)
Webpack已然成为当前web开发最重要的工具之一。首先它是一个Javascript的打包工具,但同时他也能打包包括HTML,CSS,甚至是图片等形式的资源。它能更好的控制你正在编写的App的HTTP请求,并且允许你去使用更多的资源(如Jade,Sass以及ES6)。Webpack同时允许你更容易的从npm获取安装包。
这篇文章主要面向那些对于webpack完全陌生的同学,内容将包括初始安装和配置,模块,模块加载器,插件,代码拆分以及模块热替换(HMR,hot module replacement)。如果你觉得入门视频比较有用的话,我推荐Glen Maddern的Webpack初体验作为开始学习的起点,会让你理解为什么webpack如此特殊。
为了更加后续的阅读,请确保先安装了Node.js,安装可以参考Node.js安装教程。你也在Github上下载到对应的Demo。
安装
让我们用npm和webpack新建一个项目吧:
编辑以下文件:
以上的设置只是通用配置,它会指导你的webpack将我们的入口文件
src/app.js
编译输入为/dist/bundle.js
,并且所有的.js
文件都将通过Babel从ES2015转换为ES5。为了让这个项目能运行起来,我们需要安装三个安装包,
babel-core
,webpack的加载器babel-loader
以及预处理模块babel-preset-es2015
,这些模块都是为了支持Javascript的编写。{ modules: false }
可以确保使用Tree Shaking去去除掉不必要的模块,同时会降低文件大小。npm install babel-core babel-loader babel-preset-es2015 --save-dev
最后使用下面代码更新
package.json
:运行
npm start
将会以观察模式启动webpack,在这种模式下,会持续监听我们src
文件夹下的.js
文件。控制台的输出结果显示了生成的打包后的文件,我们应该持续关注生成的文件的大小和数量。现在你可以在浏览器中访问
index.html
,将会看到“Hello webpack.”打开
dist/bundle.js
看看webpack到底做了什么事,在文件的顶部是bootstrapping模块的代码,在它下面是我们自己的模块。你可能目前还没有什么感觉webpack好处,但是你现在可以编写ES6代码并且webpack将会把各个模块打成生产所需要的包,这样所有浏览器都能访问。使用
Ctrl + C
停止webpack的服务,运行npm run build
,编译成生成环境所需要的包。注意:包的大小从2.61 kB降到了585 bytes
重新看看
dist/bundle.js
,你会发现代码变得一团糟,UglifyJS对打包后的代码进行了压缩,运行起来是没有差别的,但同时字符数是相当少的。模块
对于外部模块,webpack有多种方式去引入,其中比较重要的两种是:
import
方法require()
方法我们可以通过安装lodash来测试上述方式,并且导入到
app.js
中。运行
npm start
重启webpack并刷新index.html
,你会在页面上看到一个按照manager分好组人名的数组。接下来让我们把这个数组部分单独放在
people.js
这个模块里。我们可以以相对路径的方式将模块导入到
app.js
注意:导入像
'lodash/collection
这种不使用相对路径的,是那些通过npm安装的,从/node_modules
中引入,你自定义的模块则需要像'./people'
相对路径的方式引入,通过这种方式可以对两种模块进行区分。加载器
我们已经介绍了
babel-loader
,它是众多loader中的一种,能够告诉webpack当遇到不同的文件时如何处理。比较好的方式是将loader进行串联,加载到一个加载器中,我们通过从Javascript中引入Sass包来看看loader是如何进行工作的。Sass
这个转换器包括了三个单独的加载器和
node-sass
库:在配置文件中为
.scss
引入新的规则:注意:不管什么时候你改变了
webpack.config.js
中的加载规则,你都需要通过Ctrl + C
然后npm start
的方式重启webpack。loader以倒序的方式运行:
sass-loader
转换Sass成CSScss-loader
将CSS解析Javascript并解决依赖包问题style-loader
将CSS导出成<tag>
便签放在document下你可以将上述过程想象成函数的调用关系,一个函数运行的结果作为另一个函数的输入:
接下来让我们增加一个Sass源文件:
现在你可以在你的
app.js
中直接引入Sass文件:刷新
index.html
你会看到样式发生了变化。Javascript中的CSS
我们刚刚把Sass作为一个模块引入到我们的入口文件中。
打开
dist/bundle.js
,搜索pre {
。事实上,Sass已经被编译成一段CSS的字符串,并以模块的形式存在。当我们在我们的Javascript文件中导入这个模块时,style-loader
就会将其编译输出成内嵌的<style>
标签。我知道你在想什么?为什么要这么做?
关于这个问题,我在这个话题中不想说太多,但下面几个原因值得思考一下:
图片
最后一个关于loader的例子是关于处理图片的
url-loader
。在标准HTML文档中,图片通过
<img>
标签或者background-image
属性获得。但是通过webpack,一些小图片可以以字符串的形式存储在Javascript中。通过这种方式,你可以在预加载的时候就获取到图片,从而不需要单独的请求去请求图片。在配置文件中增加一条图片的规则:
通过
Ctrl + C
和npm start
重启服务。通过下面的命令下载一个测试图片:
现在可以在
app.js
中加载图片资源:这样页面中多了一个img,它的
src
属性包含了图片自身的data URI。同时,因为
css-loader
的缘故,通过url()
属性引入的图片,也通过url-loader
转换成行内元素。编译后变成:
模块到静态资源
现在你可以webpack是如何帮助你对将你项目中一系列的依赖资源进行打包处理的,下面这张图是webpack官网主页上的。
虽然Javascript是入口文件,但是webpack还是倾向于你的其他类型的资源像HTML, CSS, and SVG能有自己的依赖,把它们作为构建包的一部分。
插件
我们已经看过了webpack其中一个构建插件的例子,使用UglifyJsPlugin的
npm run build
脚本可以调用webpack -p
,它的作用是与webpack搭配压缩生成后的包。当loader在单个文件上操作相应变换时,插件可以在各个大型代码块上交叉运行。
公共代码
commons-chunk-plugin是另一个核心插件,搭配webpack用来创建在多个入口文件中使用的拥有公共代码的单文件模块。到目前为止,我们使用的都是单一入口和单一出口文件。但是很多real-world scenarios中更好的方法是使用多文件入口和多文件出口。
如果你在你的应用中有两个完全独立的领域但是却拥有共同的模块,举个例子,
app.js
是面向用户的,admin.js
是面向管理员的,你就可以为他们单独创建不同的入口文件,就像下面这样:注意对于结果文件,现在包含了名字,这样我们区分出两个不同的结果文件对应不同的入口文件:
app.bundle.js
和admin.bundle.js
。commonschunk
插件生成了第三个文件commons.js
,他包含了我们入口文件的公共模块。这些入口文件将会产生下列文件:
lodash/collection
模块people
模块我们可以在两个入口文件中都引入公共模块:
试试在浏览器中重新加载
index.html
和admin.html
,看看自动生成的公共模块部分。抽取CSS
另一个受欢迎的插件是extract-text-webpack-plugin,它的用途是抽取模块到对应的结果文件中。
下面我们在配置文件中修改
.scss
的规则编译成对应的Sass文件,加载CSS,接着把他们抽取到各自的CSS包中,这样就可以把它们从Javascript包中移除。重启webpack你会看到一个新的打包后的文件
app.bundle.css
,你可以照例直接引用它。刷新页面,确认CSS已经被编译过了,并从
app.bundle.js
移到了app.bundle.css
,成功了!代码拆分
我们已经看了几种代码拆分的方法:
拆分包还有其他方法:System.import和require.ensure。通过在这些函数中包含代码段,你可以创建一个在运行时按需加载的模块。这个从根本上提高了性能,因为在启动过程中不需要把所有东西都发送到客户端。
System.import
将模块名作为参数,并返回一个Promise对象。require.ensure
获取依赖关系的列表,回调函数以及可选的模块名。如果应用程序的某一部分具有很大的依赖关系,则应用程序的其余部分就不需要了,最好的方式就是拆分到各个模块中去。我们通过新建一个需要依赖d3的模块
dashboard.js
来证明这点。在
app.js
的顶部引入dashboard.js
:因为我们加载了异步模块,我们需要在配置文件中增加
output.publicPath
属性,因此webpack知道去哪里获取。运行
npm build
操作,你会看到一个新的神秘的打包文件0.bundle.js
。注意webpack为了保持诚实,通过凸现
[big]
的包来让你保持关注。这个
0.bundle.js
将会通过JSONP的请求按需加载,所以从文件目录中获取将不在有效,我们需要启动一个服务来获取文件。打开浏览器,输入http://localhost:8001/
加载一秒钟后,你会获得一个GET请求,我们动态生成了
/dist/0.bundle.js
文件,在控制台上打印除了"Loaded!",成功!webpack开发服务器
当文件改变时,实时地重新加载能提高开发者的开发效率。只要安装它,并且以
webpack-dev-server
的形式启动,就可以体验啦。修改
package.json
中的start
脚本:重新运行
npm start
,在浏览器中打开http://localhost:8080试着去改变src目录中任何文件,如改变
people.js
中的任意一个名字,或者style.scss
中的任意样式,去看看它如何实时改变。热模块替换(热更新)
如果你对实时重新加载印象深刻,那么hot module replacement(HMR)一定会让你吃惊不已。
现在是2017年了,你在工作中已经可以在单页面应用中使用全局状态了。在开发过程中,你可能会对组件进行许多小的修改,并且希望能在浏览器中看到修改后生成的结果,这样可以实时去更改。但是通过刷新页面或者实时热更新并不能改变全局的状态,你就必须重头开始。但是HMR永远地改变了这一问题。
最后对
package.json
中的start
脚本做修改:在
app.js
中告诉webpack去接受这个模块以及对应依赖的热更新。注意:
webpack-dev-server --hot
设置了module.hot
为true
,但只是在开发过程中。当以生产模式打包时,module.hot
被设成了false
,这样这些包就被从结果中抽离了。在
webpack.config.js
中增加一个NamedModulesPlugin
插件,去改善控制台的记录功能。最后我们在页面中增加一个
<input>
元素,我们可以在里面增加一些文字,用来确保我们更改自己模块时页面不会刷新。运行
npm start
重启服务,观察热更新如何工作吧。为了实验,在input框中输入“HMR Rules”,接着改变一个
people.js
中的名字,你会发现页面在不刷新也能做出修改,而忽略input的状态。这只是一个简单的例子,但是希望你能看到其广泛的用途。在诸如React的开发模式中,你可能有很多"哑巴"组件是与他们的状态分离开的,通过热更新,这些组件将不会失去状态,也能实时更新,因此你将获得及时的反馈。
热更新CSS
修改
style.scss
文件中<pre>
元素的背景颜色,你发现他并没有被HMR替换。事实证明当你使用
style-loader
时,CSS的热更新将会免费为你提供而不需要你做任何特殊处理。我们只需要断开CSS模块与最终抽取的包之间的链接,这个包是无法被替换的。如果我们将Sass规则恢复到原始状态,并从插件列表中删除extractCSS,那么您也可以看到Sass的热重新加载。
HTTP/2
使用像webpack这样的模块打包工具的主要好处之一是,您可以通过控制资源的构建方式以及在客户端上的获取方式,从而帮助你提高性能。多年以来,它被认为是最佳实践,通过连接文件减少客户端请求。现在还是有效,但是HTTP2在单一请求中发送多文件,因此连接文件的方式不不再是"银弹"。你的应用程序实际上可以从多个小文件单独缓存,但客户端可以获取单个更改的模块,而不必再次获取大部分相同内容的整个包。
Webpack的创始人Tobias Koppers的撰写了一篇内容丰富的帖子,解释了为什么打包仍然很重要,即使在HTTP/2时代。
想了解更多请参考webpack & HTTP/2
写在结尾的话
我真心希望你已经发现这个介绍webpack 2的文章对你有帮助,并能够开始很好使用它。围绕webpack的配置,加载程序和插件可能需要一些时间,但是了解这个工具的工作原理后会对你有很大帮助。
文档仍在进行更新中,但如果您想将现有的Webpack1项目移到Webpack2,则可以参考Migrating from v1 to v2。
webpack是否是你打包的选择,从评论中你就可以知晓。
本文由Scott Molinari,Joan Yin和Joyce Echessa进行了同行评审。 感谢SitePoint的同行评议人员,使SitePoint内容成为最棒的内容!
本文翻译自A Beginner’s Guide to Webpack 2 and Module Bundling
翻译者:Allen Gong
The text was updated successfully, but these errors were encountered: