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

什么是 Webpack?

简单来说,Webpack 就是一个针对 JavaScript 代码的模块打包工具。但是自发布以来,它演变成了一个针对所有前端代码的管理工具(不管是其本身有意还是社区的意愿)。

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

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

Webpack 通过一个大胆的询问试图减轻开发者的负担:如果开发过程的某个部分可以自己管理依赖会怎么样?如果我们可以以这样一种方式来简单地写代码:构建过程仅基于最后所需要的东西来管理它自己,会怎么样?

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

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

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

总而言之,如果你曾经挣扎于下面这些情况中的其中之一:

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

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

第一步

在这篇教程里我们将使用 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 写的很棒的文章

Webpack2入门的更多相关文章

  1. webpack2入门概念

    webpack是一种JavaScript应用模块化打包工具,它配置起来简单易上手,因此很多企业工程化代码都使用它来打包.在具体介绍如何使用webpack之前,先来介绍下webpack的四个核心概念. ...

  2. 【webpack2】-- 入门与解析

    每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档. webpack是基于node的.先安装最新的node. 1.初始化 安装node后,新建一个目录,比如ht ...

  3. webpack2教程--从入门到放弃

    开车之前,先介绍一些npm的命令: :D 进入D盘 mkdir webapp 创建webapp文件夹 cd webapp 进入webapp文件夹 mkdir webapp && cd ...

  4. webpack2的一些使用入门

    首先创建一个webpack文件夹我取名叫webpackVue(为了后续把vue集成进来) 1.首先用npm初始化一下,在这个目录下,执行npm init 2.npm install webpack - ...

  5. webpack入门必知必会

    关于 微信公众号:前端呼啦圈(Love-FED) 我的博客:劳卜的博客 知乎专栏:前端呼啦圈 前言 这是我第一篇介绍webpack的文章,先从一个入门教程开始吧,后续会有更多相关webpack的文章推 ...

  6. webpack入门详解

    webpack入门详解(基于webpack 3.5.4  2017-8-22) webpack常用命令: webpack --display-error-details    //执行打包 webpa ...

  7. webpack快速入门——如何安装webpack及注意事项

    1.window+R键,输入cmd打开命令行工具,输入 mkdir XXXX(XX:文件夹名): 2.cd XXX 进入刚刚创建好的文件夹里,输入cnpm install -g webpack (安装 ...

  8. webpack快速入门——配置文件:入口和出口,多入口、多出口配置

    1.在根目录新建一个webpack.config.js文件,然后开始配置: const path = require('path'); module.exports={ //入口文件的配置项 entr ...

  9. webpack快速入门——Json配置文件使用

    在实际工作中,我们的项目都会配置一个Json的文件或者说API文件,作为项目的配置文件. 有时候你也会从后台读取到一个json的文件,这节课就学习如何在webpack环境中使用Json. 如果你会we ...

随机推荐

  1. select简单示例,有注释

    全部都在代码中: import select import socket import queue """ 简单的select 实现echo server 个人理解: s ...

  2. 牛客网Java刷题知识点之拥塞发生的主要原因、TCP拥塞控制、TCP流量控制、TCP拥塞控制的四大过程(慢启动、拥塞避免、快速重传、快速恢复)

    不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号:   大数据躺过的坑      Java从入门到架构师      人工智能躺过的坑          ...

  3. Navicat Premium v12.0.23.0 安装,使用激活码激活

    1 下载 可以直接官网下载安装包,也可以直接到我的云盘下载 下载地址:https://pan.baidu.com/s/1apwU9cIKBTr-z0CuJEJ9gg 文件包中包含下面的文件: 2 安装 ...

  4. TOJ 1717 WOJ

    描述 Alex likes solving problems on WOJ (http://acm.whu.edu.cn/oak). As we all know, a beautiful ballo ...

  5. java 中String编码和byte 解码总结——字节流和字符流

    1.InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符 InputStreamReader(InputStream in, Strin ...

  6. 阅读redis源代码的一些体会

    最近在学习redis及阅读redis等程序的源码时,有一些收获,特记录到下面. 1.第一步,阅读源代码借助最好可以跟踪的工具去读,如sourceinsight. 我使用的是windows7环境,又因为 ...

  7. 新版本火狐 ,Chrome不支持showModalDialog解决办法

    平常的网站中,有时我们会希望使用者按下按钮后开启一个保持在原窗口前方的子窗口,在IE中,我们可以使用showModalDialog来达成,但是chrome早就不支持showModalDialog,最近 ...

  8. 微信token验证源码分享(c#版)

    在开发时遇到一个问题: 上线后提交申请微信提示"您的服务器没有正确响应token验证...",我查看日志发现根本就没有接收到来自微信的参数. 后来我又记录了微信请求方式和请求的字符 ...

  9. Sql server 0x80131904

    这是一个比较让人纠结的错误. 背景:添加了网站的分支,要拿一个现有的数据库做一个新的数据库的东东. 首先就是还原备份,然后做代码的修改.然而在登录的时候报了这个错误.0x80131904 什么插入的列 ...

  10. Java集合篇一:ArrayList

    package com.test.collection; /** * 自定义ArrayList容器 * * 1.实现原理:底层封装数组 * * 2.查询 * LinkList 相较 ArrayList ...