Webpack2入门

webpack 2 将在其文档完成以后正式发布。但这并不意味着不能够开始使用它,若是你知道怎么配置的话。javascript

什么是 Webpack?

简单来讲,Webpack 就是一个针对 JavaScript 代码的模块打包工具。可是自发布以来,它演变成了一个针对全部前端代码的管理工具(不论是其自己有意仍是社区的意愿)。css

旧的任务运行工具处理方式:HTML、CSS 和 JavaScript 都是分离的。必须分别对每一项进行管理,而且还要确保全部东西正确地部署到生产环境。html

像 Gulp 这样的任务运行工具能够操做不少不一样的预处理器和编译器,可是在全部状况下,它都须要接收一个源码输入并将其处理成一个编译好的输出。然而,它是在不关心整个系统的状况下逐个去处理的。这就是开发者的负担了:检查任务运行工具备无遗漏的地方,并为全部改动的部分找到正确的方式,将它们在生产环境上协调一致。前端

Webpack 经过一个大胆的询问试图减轻开发者的负担:若是开发过程的某个部分能够本身管理依赖会怎么样?若是咱们能够以这样一种方式来简单地写代码:构建过程仅基于最后所须要的东西来管理它本身,会怎么样?vue

Webpack 处理方式:若是是 Webpack 知道的代码,那么它就只会打包实际在生产环境当中使用的部分。java

若是你是过去几年里 Web 社区当中的一员,那么你确定已经知道首选解决问题的方法:使用 JavaScript 构建。因此 Webpack 试图经过用 JavaScript 传递依赖来使构建过程变得更加容易。可是它设计的精妙之处并不在于简单的代码管理部分,而在于它的管理层面是百分百有效的 JavaScript(还有 Node 特性)。Webpack 使你有机会写出一些对总体系统有更好感知的 JavaScript 代码。node

换句话说:你不是为了 Webpack 写代码,而是为了你的项目写代码。并且 Webpack 在保持进步(固然包括某些配置)。react

总而言之,若是你曾经挣扎于下面这些状况中的其中之一:webpack

  • 不当心将一些不须要的样式表或者 JS 库引入生产环境,致使项目体积变大
  • 遇到做用域问题 - 不论是来自 CSS 仍是 JavaScript
  • 不停寻找一个好的系统好让你能够在 JavaScript 代码里使用 Node 或 Bower 的模块,或者依赖一系列疯狂的后端配置来正确地使用那些模块
  • 须要优化资源分发机制却又担忧会破坏掉某些东西

…那么你就能够受益于 Webpack 了。它经过让 JavaScript 取代开发者的大脑来关心依赖和加载顺序,轻松地解决了上面这些问题。最好的部分是什么?Webpack 甚至能够在服务端无缝运行,这意味着你仍然可使用 Webpack 来构建渐进式加强的网站。nginx

第一步

在这篇教程里咱们将使用 Yarnbrew install yarn)来替代 npm,但这彻底取决于你本身,它们作的是一样的事情。打开到项目文件夹,在命令行窗口运行下面的命令添加 Webpack 2 到全局包和本地项目里:

1
2
yarn global add webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10
yarn add --dev webpack@ 2.1.0-beta.25 webpack-dev-server@2.1.0-beta.10

而后在根目录新建一个 webpack.config.js 文件用来声明 Webpack 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';
 
const webpack = require("webpack");
 
module.exports = {
context: __dirname + "/src",
entry: {
app: "./app.js",
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

注意:__dirname指的是根目录。

还记得 Webpack “知道”项目里发生了什么吗?它是经过读取你的代码知道的(不用担忧,它签署了一份保密协议)。Webpack 基本作了下面这些事情:

  1. 从 context 对应的文件夹开始…
  2. …寻找 entry 里全部的文件名…
  3. …而后读取它们的内容。在解析代码时,每个经过 import(ES6) 或 require()(Node) 引入的依赖都会被打包到最终的构建结果当中。它会接着搜索那些依赖,以及那些依赖的依赖,直到“依赖树”的叶子节点 — 只打包它所须要的依赖,没有其余的东西。
  4. 接着,Webpack 将全部东西打包到 output.path 对应的文件夹里,使用 output.filename 对应的命名模板来命名([name] 被 entry 里的对象键值所替代)

因此若是 src/app.js 文件看起来像下面这样的话(假设提早运行了 yarn add --dev moment):

1
2
3
4
5
'use strict';
import moment from 'moment';
var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log( rightNow );
// "October 23rd 2016, 9:30:24 pm"

接着运行:

1
webpack -p

注意:p 标记表示“生产(production)”模式而且会压缩或丑化(uglify/minify)输出。

而后它将输出一个 dist/app.bundle.js 文件,做用就是打印出当前日期和时间到控制台。注意到 Webpack 自动知道了 'moment' 指的是什么(但若是已经有一个 moment.js 在你的目录当中,那么 Webpack 默认就会优先使用这个而不是 moment 的 Node 模块)。

处理多个文件

你只须要经过修改 entry 对象就能够指定任意数量所指望的 entry 或 output 点。

多个文件,打包在一块儿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';
 
const webpack = require("webpack");
 
module.exports = {
context: __dirname + "/src",
entry: {
app: ["./home.js", "./events.js", "./vendor.js"],
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

全部文件会按数组顺序一块儿打包到 dist/app.bundle.js 一个文件当中。

多个文件,多个输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const webpack = require("webpack");
 
module.exports = {
context: __dirname + "/src",
entry: {
home: "./home.js",
events: "./events.js",
contact: "./contact.js",
},
output: {
path: __dirname + "/dist",
filename: "[name].bundle.js",
},
};

另外,你还能够选择打包成多个 JS 文件来将应用拆解成几个部分。像上面这样作就能够打包成三个文件:dist/home.bundle.jsdist/events.bundle.js 和 dist/contact.bundle.js

进阶自动打包

若是你正在将应用拆解,打包成多个 output 的话(若是应用的某部分有大量不须要提早加载的 JS 的话,这样作会颇有用),那么在这些文件里就有可能出现重复的代码,由于在解决依赖问题的时候它们是互相不干预的。幸亏,Webpack 有一个内建插件 CommonsChunk 来处理这个问题:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
// …
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
minChunks: 2,
}),
],
// …
};

如今,在 output 的文件里,若是有任意模块加载了两次或更多(经过 minChunks 设置该值),它就会被打包进一个叫 commons.js 的文件里,后面你就能够在客户端缓存这个文件了。固然,这确定会形成一次额外的请求,可是却避免了客户端屡次下载相同库的问题。因此在不少场景下,这都是提高速度的举措。

开发

实际上 Webpack 有它本身的开发服务器,因此不管你正在开发一个静态网站,或者只是正在原型化前端阶段,这个服务器都是完美可用的。想要运行它,只须要在 webpack.config.js 里添加一个 devServer 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
context: __dirname + "/src",
entry: {
app: "./app.js",
},
output: {
filename: "[name].bundle.js",
path: __dirname + "/dist/assets",
publicPath: "/assets", // New
},
devServer: {
contentBase: __dirname + "/src", // New
},
};

如今新建一个 src/index.html 文件,加入下面这行:

1
<script src="/assets/app.bundle.js"></script>

而后在命令行中,运行:

1
webpack-dev-server

服务器如今就运行在了 localhost:8080 上。注意 script 标签中的 /assets 对应的是 output.publicPath 的值 - 能够随便填成你想要的命名(若是须要一个 CDN,这就颇有用了)。

当你更改 JavaScript 代码的时候,Webpack 就会实时更新页面而无需手动刷新浏览器。可是,任何对webpack.config.js 的更改都须要重启服务器才能够生效。

全局可访问方法

须要在全局命名空间里使用某些本身的方法吗?只需简单地在 webpack.config.js 里设置 output.library

1
2
3
4
5
module.exports = {
output: {
library: 'myClassName',
}
};

…这样就会把打包结果绑定到一个 window.myClassName 实例上。因此使用这种命名做用域,就能够调用 entry 点里面的方法了(能够阅读文档获取更多关于这个配置的信息)。

Loaders

目前为止,咱们所讲到的都是关于 JavaScript 代码的使用。从 JavaScript 代码开始是很是重要的,由于这是 Webpack 惟一使用的语言。咱们能够处理任何文件类型,只要将它们传进 JavaScript 代码中。这个功能用 Loaders 来实现。

一个 loader 能够指向一个像 Sass 的预处理器,或者像 Babel 的编译器。在 NPM 中,它们一般是像 sass-loader 或babel-loader 这样命名为 *-loader

Babel + ES6

若是咱们想要在项目中经过 Babel 来使用 ES6,首先要在本地正确地安装一些 loader:

1
yarn add --dev babel-loader babel-core babel-preset-es2015

…而后把它们添加进 webpack.config.js 好让 Webpack 知道哪里使用它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
// …
module: {
rules: [
{
test: /\.js$/,
use: [{
loader: "babel-loader",
options: { presets: [ "es2015"] }
}],
},
 
// Loaders for other file types can go here
],
},
// …
};

一个给 Webpack 1 用户的提示:Loaders 的核心概念仍然是同样的,但语法上作了改进。在他们完成文档以前这可能不是确切的首选语法。

这样作就能够为 /\.js$/ 正则表达式寻找以 .js 结尾的文件,最后经过 Babel 编译加载。Webpack 依赖正则表达式给予你完整的控制 - 但它不会限制你的文件后缀,或者假设你的代码必须以某种特定形式组织起来。举个例子:也许你的/my_legacy_code/ 文件夹里的代码不是用 ES6 写的,那么你就能够把上面的 test 修改成/^((?!my_legacy_code).)*\.js$/,这样就能够绕过这个文件夹,其他文件用 Babel 编译。

CSS + Style Loader

若是咱们只想加载应用须要的 CSS,也能够那么作。假设有一个 index.js 文件,在里面引入:

1
import styles from './assets/stylesheets/application.css';

就会获得一个错误:You may need an appropriate loader to handle this file type。记住 Webpack 只能读取 JavaScript,因此咱们必须安装正确的 loader:

1
yarn add --dev css-loader style-loader

而后在 webpack.config.js 里添加一个规则:

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [ "style-loader", "css-loader"],
},
// …
],
},
};

这些 loader 会以数组逆序运行。这意味着 css-loader 会在 style-loader 以前运行。

你可能注意到甚至在生产构建的结果中,也把 CSS 打包进了 JavaScript 里面,而且 style-loader 手动地将样式写进了 <head> 中。乍一看这可能有点奇怪,但当你考虑足够多的时候就会慢慢发现这实际上是有道理的。你保存了一个头部请求(在某些链接上节省宝贵的时间),而且若是你用 JavaScript 来加载 DOM,这么作基本上就消除了它自身的无样式闪屏问题。

还注意到 Webpack 已经经过把全部文件打包成一个从而自动解决了全部的 @import 查询问题(比起依赖 CSS 默认的引入致使没必要要的头部请求和缓慢的资源加载,这么作显然更好)。

从 JS 里加载 CSS 至关爽,由于你能够用一种强有力的新方式去模块化 CSS 代码了。假设你只经过 button.js 加载了button.css,这就意味着若是 button.js 没有实际用到的话,它的 CSS 也不会打包进咱们的生产构建结果。若是你坚持使用像 SMACSS 或者 BEM 那样的面向组件的 CSS,就会知道把 CSS 和 HTML + JavaScript 代码放更近的价值了。

CSS + Node modules

咱们能够在 Webpack 里用 Node 的 ~ 前缀去引入 Node Modules。假设咱们提早运行了 yarn add normalize.css,就能够这么用:

1
@ import "~normalize.css";

这样就能够全面使用 NPM 来管理第三方样式库(版本及其余)而对咱们而言就无需复制粘贴了。更进一步的是,Webpack 打包 CSS 比使用默认的 CSS 引入有着显而易见的优点,让客户端远离没必要要的头部请求和缓慢的资源加载。

更新:这个部分和下面的部分为了更准确都进行了更新,不用再困扰于使用 CSS Modules 去简单地引入 Node Modules 了。感谢 Albert Fernández 的帮助!

CSS Modules

你可能已经据说过 CSS Modules,它将 CSS(Cascading Style Sheets)里的 C(Cascading)给提出来了。它只在用 JavaScript 构建 DOM 的时候使用有最佳效果,但本质上来讲,它巧妙地将 CSS 在加载它的 JavaScript 里做用域化了(点击这个连接学习更多相关知识)。若是你计划使用它,CSS Modules 对应的 loader 是css-loaderyarn add --dev css-loader):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
{ loader: "css-loader", options: { modules: true } }
],
},
// …
],
},
};

注意:对于 css-loader 咱们使用了展开的对象语法来为它添加配置。你能够写简单的字符串表明使用默认配置,style-loader 就仍是这么作的。


值得注意的是实际上在使用 CSS Modules 引入 Node Modules 的时候能够去掉 ~ 符号(如@import "normalize.css";)。可是,当 @import 你本身的 CSS 时可能会遇到错误。若是你获得了 “can’t find ___” 这样的错误,尝试添加一个 resolve 对象到 webpack.config.js 里,好让 Webpack 更好地理解你预期的模块顺序。

1
2
3
4
5
6
7
const path = require("path");
module.exports = {
//…
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"]
},
};

首先指定了咱们本身的源文件目录,而后是 node_modules。这样子 Webpack 解决起来就会处理得更好一些,按照那个顺序先找咱们的源文件目录,而后是已安装的 Node Modules(分别用你本身的源码和 Node Modules 目录替换其中的src 和 node_modules)。

Sass

想用 Sass?没问题,安装:

1
yarn add --dev sass-loader node-sass

而后添加另外一条规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
// …
module: {
rules: [
{
test: /\.(sass|scss)$/,
use: [
"style-loader",
"css-loader",
"sass-loader",
]
}
// …
],
},
};

接下来当 JavaScript 调用 import 引入一个 .scss 或 .sass 文件时,Webpack 就会作它该作的事情了。

分开打包 CSS

或许你正在处理渐进式加强的网站,又或许由于其余的缘由你须要一个分离的 CSS 文件。咱们能够简单地实现,只须要在配置里用 extract-text-webpack-plugin 替换掉 style-loader,而无需改变其余任何代码。以 app.js 文件为例:

1
import styles from './assets/stylesheets/application.css';

本地安装插件(咱们须要这个的测试版本,2016年10月发布):

1
yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4

添加到 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
// …
module: {
rules: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
loader: 'css-loader?importLoaders=1',
}),
},
 
// …
]
},
plugins: [
new ExtractTextPlugin({
filename: "[name].bundle.css",
allChunks: true,
}),
],
};

如今运行 webpack -p 的时候就能够看到一个 app.bundle.css 文件出如今 output 目录里了。像往常同样简单地添加一个 <link> 标签到 HTML 文件里就能够了。

HTML

你可能已经猜到,Webpack 还有一个 html-loader 插件。可是,当咱们开始用 JavaScript 加载 HTML 的时候,这实际上是一个能够分支成不一样方法的地方,并且我想不到一个单独的简单示例能够覆盖全部下一步操做的可能性。一般,你可能会在用 ReactAngularVue 或者 Ember 构建的大型系统中加载诸如 JSXMustache 或者 Handlebars 这样偏向 JavaScript 的模板 HTML;或者你可能使用一个像 Pug(之前的 Jade)或者 Haml 这样的 HTML 预处理器;或者你可能只是想简单地将源文件目录里的 HTML 复制到构建结果目录里。无论你想作什么,我没办法假设。

因此我准备在此结束本教程:你能够用 Webpack 加载 HTML,但这一点你必须本身根据你的架构作出决策,不论是我仍是 Webpack 都没办法帮到你。不过使用上述例子做为参考并在 NPM 上找到正确的 loader 应该足够让你继续下去了。

从模块角度思考

为了最大程度发挥 Webpack 的做用,你不得不从模块的角度去思考(小、可复用、自包含进程),一件件事情慢慢去作好。这意味着下面这样的东西:

1
2
└── js/
└── application.js // 300KB of spaghetti code

把它变成:

1
2
3
4
5
6
7
8
9
10
11
12
└── js/
├── components/
│ ├── button.js
│ ├── calendar.js
│ ├── comment.js
│ ├── modal.js
│ ├── tab.js
│ ├── timer.js
│ ├── video.js
│ └── wysiwyg.js
└── application.js // ~ 1KB of code; imports from ./components/

结果是干净且可复用的代码。每一个独立的组件取决于导入自身的依赖,并按照它想要的方式导出到其余模块。配合 Babel + ES6 使用,还能够利用 JavaScript Classes 作出更好的模块化,而且不要去想它,做用域只是在起做用。

想知道更多关于模块的内容,能够看这篇 Preethi Kasreddy 写的很棒的文章

相关文章
相关标签/搜索