深入理解学习webpack

webpack

深入理解学习webpack

在之前的文档中只是列出了webpack的配置文件,这样做只是说能够使用它,并没有理解webpack的配置文件中各项key:value的具体作用和详细配置选项。所以在深入学习vue之前,有必要学习webpack的理念。

写在前面

仍然需要注意环境变量的问题,否则无法正确的安装!

webpack 核心概念

将模块按照依赖和规则打包成符合生产环境的前段资源,还可以将按需加载的模块进行代码分割,等到实际需要时,再异步加载。通过loader转化,所有的资源都可以视为模块。CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。

安装

使用Node.js的LTS最新版本,这样能保证不缺失webpack的功能或相关package包。

本地局部安装:

1
2
3
4
5
6
# 安装 latest release
npm install --save-dev webpack
# 简写模式
npm install -D webpack
# 安装特定版本
npm install --save-dev webpack@<version>

全局安装:

1
npm install -g webpack

注意:不推荐全局安装 webpack。这会锁定 webpack 到指定版本,并且在使用不同的 webpack 版本的项目中可能会导致构建失败。但是全局安装可以在命令行调用 webpack 命令。

【补充】npm install 安装模块参数说明:

1
2
3
4
5
-g, --global 全局安装(global)
-S, --save 安装包信息将加入到dependencies(生产阶段的依赖)
-D, --save-dev 安装包信息将加入到devDependencies(开发阶段的依赖),所以开发阶段一般使用它
-O, --save-optional 安装包信息将加入到optionalDependencies(可选阶段的依赖)
-E, --save-exact 精确安装指定模块版本

npm 相关的更多命令参考这篇文章:npm常用指令详解

然后在根目录下创建一个 webpack.config.js 文件后,你可以通过配置定义webpack的相关操作。

入口(Entry)

顾名思义,告诉webpack从哪开始,遵循依赖关系。

webpack.config.js的配置项
单个入口(简写)语法:

1
2
3
module.exports = {
entry: './src/main.js'
};

对象语法:

1
2
3
4
5
6
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue']
}
};

将vue作为库vendor打包,业务逻辑代码作为app打包,实现了多个入口,同时也可以将多个页面分开打包。

多页面应用程序通常使用对象语法构建。对象语法是“可扩展的 webpack 配置”,可重用并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点(concern)从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如webpack-merge)将它们合并。

注:vue-cli 生成的模板中build文件夹下有四个配置文件:

  • webpack.base.conf.js:基本配置
  • webpack.dev.conf.js:开发阶段配置
  • webpack.prod.conf.js:准生产阶段配置
  • webpack.test.conf.js:测试配置

后三个文件通过webpack-merge插件合并了基本配置,将不同环境下的配置拆分多个文件,这样更加方便管理。

出口(Output)

控制 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个入口起点,但只指定一个输出配置。

在 webpack 中配置output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:

  • output.filename:编译文件的文件名;
  • output.path对应一个绝对路径,此路径是你希望一次性打包的目录。

单个入口:

1
2
3
4
5
6
7
8
const path = require('path');
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build') //__dirname + '/build'
}
}

多个入口:
如果你的配置创建了多个 “chunk”(例如使用多个入口起点或使用类似CommonsChunkPlugin 的插件),你应该使用以下的替换方式来确保每个文件名都不重复。

  • [name] 被 chunk 的 name 替换。
  • [hash] 被 compilation 生命周期的 hash 替换。
  • [chunkhash] 被 chunk 的 hash 替换。
1
2
3
4
5
6
7
8
9
10
11
12
13
    const path = require('path');
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build')
}
}

// 写入到硬盘:./build/app.js, ./build/vendor.js

!!!关于出口项的配置,单出口的path设置过之后发现无效,先放下,过后研究。

加载器(Loaders)

loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你在 JavaScript 中 require() CSS文件!

在你的应用程序中,有三种方式使用 loader:

主要说明一下使用webpack.config.js配置,使用loader需要在module的rules下配置相应的规则,以css-loader的webpack.config.js为例说明:

1
2
3
4
5
6
7
module.exports = { 
module: {
rules: [
{test: /\.css$/, use: 'css-loader'}
]
}
};

以下的三种配置方式等效:

1
2
3
4
5
6
7
8
{test: /\.css$/, use: 'css-loader'}
{test: /\.css$/, loader: 'css-loader',options: { modules: true }}
{test: /\.css$/, use: {
loader: 'css-loader',
options: {
modules: true
}
}}

注:loader/query可以和options可以在同一级使用,但是不要使用use和options在同一级使用。

CSS样式分离

对CSS文件的打包,可以像其他模块一样将CSS引入avaScript 代码中,同时用css-loader(像 JS 模块一样输出 CSS),也可以选择使用ExtractTextWebpackPlugin(将打好包的 CSS 提出出来并输出成 CSS 文件)。[这个尚未尝试过]

引入CSS:

1
import 'bootstrap/dist/css/bootstrap.css';

安装css-loader和style-loader:

1
npm install --save-dev css-loader style-loader

在 webpack.config.js 中配置如下:

1
2
3
4
5
6
7
8
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
}

资源路径处理

图片文件不是一个 JavaScript 文件,你需要配置 Webpack 使用file-loader或者url-loader去处理它们。使用它们的好处:

  • file-loader 可以指定要复制和放置资源文件的位置。更方便的管理图片文件,使用相对路径而不用担心部署URL时的问题。使用正确的配置,Webpack 将会在打包输出中自动重写文件路径为正确的URL。
  • 当图片文件小于更定的阈值时,会将文件转换为内联的 base-64 URL。这会减少小文件的 HTTP 请求。如果文件大于该阈值,会自动的交给 file-loader 处理。

安装 file-loader 和 url-loader:

1
npm install --save-dev file-loader url-loader

配置说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
//使用版本哈希命名以获得更好的缓存
name: 'img/[name]_[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}

插件(Plugins)

loader 仅在每个文件的基础上执行转换,而插件常用于(但不限于)在打包模块的“compilation”和“chunk”生命周期执行操作和自定义功能(更多关于自定义功能[https://doc.webpack-china.org/concepts/plugins/])

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,你需要使用 new 创建实例来调用它。

生产环境构建

对于Vue来说,在生产环境构建过程中压缩应用代码和使用Vue生产环境部署-删除警告,去除Vue.js的警告。参考vue-loader文档中的配置说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (process.env.NODE_ENV === 'production') {
// http://vue-loader.vuejs.org/zh-cn/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}

开发过程中使用这些配置的设置显得十分的琐碎,这是我们不愿意看到的,所以需要使用环境变量动态构建,我们也可以使用两个分开的 Webpack 配置文件,一个用于开发环境,一个用于生产环境,类似于vue-cli中使用 webpack-merge 合并配置的方式。

[!!!没看懂] 可以使用 Node.js 模块的标准方式:在运行 webpack 时设置环境变量,并且使用 Node.js 的process.env来引用变量。NODE_ENV变量通常被视为事实标准(查看这里)。使用cross-env包来跨平台设置(cross-platform-set)环境变量。

安装cross-env:

1
npm install --save-dev cross-env

设置package.json中的scripts字段:

1
2
3
4
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}

这里使用了cross-env插件,cross-env使得你可以使用单个命令,而无需担心为平台正确设置或使用环境变量。

模块热替换

该功能会在应用程序运行过程中替换、添加或删除模块,无需重新加载页面。在独立模块变更后,无需刷新整个页面,就可以更新这些模块,极大地加速了开发时间。

使用 webpack-dev-server 插件,可以提供一个服务器和实时重载功能。这是一个小型的node.js Express服务器。

安装 webpack-dev-server:

1
npm install --save-dev webpack-dev-server

使用 webpack-dev-server ,方式如下:

1
webpack-dev-server --open

上述命令自动在浏览器中打开 http://localhost:8080。

webpack.config.js配置:

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
...
devServer: {
historyApiFallback: true, // 任意的 404 响应都替代为 index.html
hot: true, // 启用 webpack 的模块热替换特性
inline: true // 启用内联模式
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
...
}

更多的配置说明:DevServer

提取CSS文件

extract-text-webpack-plugin可以将vue文件内的<style>提取,以及JavaScript中导入的CSS提取为单个CSS文件。具体配置文档见:extract-text-webpack-plugin

安装:

1
npm install --save-dev extract-text-webpack-plugin

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
]
}

同时支持配置生成多个CSS文件,这样可以将业务逻辑代码和引用的样式组件库分离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const ExtractTextPlugin = require('extract-text-webpack-plugin');

// Create multiple instances
const extractCSS = new ExtractTextPlugin('stylesheets/[name]-one.css');
const extractLESS = new ExtractTextPlugin('stylesheets/[name]-two.css');

module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: extractCSS.extract([ 'css-loader', 'postcss-loader' ])
},
{
test: /\.less$/i,
use: extractLESS.extract([ 'css-loader', 'less-loader' ])
},
]
},
plugins: [
extractCSS,
extractLESS
]
};

clean-webpack-plugin

在编译前,删除之前编译结果目录或文件:

1
npm install --save-dev clean-webpack-plugin

配置:

1
2
3
plugins: [
new CleanWebpackPlugin(['dist'])
]

这样构建的时候可以自动删除之前编译的代码。

解析(Resolve)

这些选项能设置模块如何被解析。webpack 提供合理的默认值,但是还是可能会修改一些解析的细节。

1
2
3
4
5
6
7
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.join(__dirname, 'src')
},
extensions: ['.js', '.json', '.vue', '.css']
}

使用最多的就是别名(alias)和自动解析确定的扩展(extensions),例如上面的@可以代替项目中src的路径,例如:

1
import tab from '@/components/tab.vue'

例如引用src/components目录下的tab.vue组件,不需要通过../之类的计算文件相对路径。这里的extensions可以实现引入模块时不加文件的扩展名:

1
import tab from '@/components/tab'

至此回顾了项目devDependencies依赖中常用的模块:

1
2
3
4
5
6
7
8
webpack 
css-loader / style-loader //CSS样式分离
file-loader / url-loader //资源路径处理
cross-env //!!!没有看懂,暂时搁置
webpack-dev-server //模块热替换,node服务器使用
extract-text-webpack-plugin //提取css文件,将业务逻辑与样式组件库分离
clean-webpack-plugin //构建前删除之前编译的代码
resolve alias/extensions 代替src的路径及忽略文件扩展名

webpack中如何使用ES6?

暂时缺省。

webpack中如何使用vue?

介绍webpack中vue相关的插件。

Vue2文件比较

npm安装:

1
npm install --save vue

vue2 经过2.2版本升级后,文件变成了8个:
| UMD | CommonJS | ES Module |
| :———: | :——–: | :——–: | :——–: |
| 独立构建 vue.js | vue.common.js | vue.esm.js
| 运行构建 vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js

vue.min.js 和 vue.runtime.min.js 都是对应的压缩版。

  • AMD:异步模块规范
  1. 没有单独提供AMD模块的版本,但是UMD版本中进行了包装,可以直接用作AMD模块,使用方法如下:
1
2
3
4
5
6
define(["Vue"],function(Vue) {
function myFn() {
...
}
return myFn;
});
  • CommonJS:node中常用的模块规范,通过require引入模块,module.exports导出模块。
1
2
3
4
5
6
...
function Vue$3() {
...
}
...
module.exports = Vue$3;
  • UMD: 通用模块规范兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范:
1
2
3
4
5
6
7
8
9
10
11
12
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Vue = factory());
}(this, (function () { 'use strict';
...
function Vue$3() {
...
}
...
return Vue$3;
})));
  • ES Module:ES6在语言标准的层面上,实现的模块功能。模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
1
2
3
4
5
...
function Vue$3() {
...
}
export default Vue$3;

总结:

  • vue.js 和 vue.runtime.js 可以直接 CDN 引用;
  • vue.common.js和vue.runtime.common.js可以使用Webpack1 / Browserify 打包构建;
  • vue.esm.js和vue.runtime.esm.js可以使用Webpack2 / rollup 打包构建。

vue有两种构建方式,独立构建和运行时构建。主要的区别在于前者包含模板编译器而运行构建不包含。模板编译器的功能是将模板字符串编译为纯 JavaScript 的渲染函数。如果组件中使用 template 选项,那就需要编译器。

  1. 独立构建包含模板编译器并支持 template 选项。它也依赖于浏览器的接口的存在,所以不能使用它来为服务器端渲染。
  2. 运行时构建不包含模板编译器,因此不支持 template 选项,只能用 render 选项,但即使使用运行时构建,在单文件组件中也依然可以写模板,因为单文件组件的模板会在构建时预编译为 render 函数。运行时构建比独立构建要轻量30%,只有 17.14 Kb min+gzip大小。

独立构建方式可以这样使用template选项:

1
2
3
4
5
6
7
8
import Vue from 'vue'
new Vue({
template: `
<div id="app">
<h1>Basic</h1>
</div>
`
}).$mount('#app')

使用ES Module规范,默认NPM包导出的是运行时构建。为了使用独立构建在webpack配置中添加以下的别名alias:

1
2
3
4
5
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}

vue-loader

安装:

1
npm install --save-dev vue-loader vue-template-compiler

vue-loader依赖于 vue-template-compiler

vue-loader是一个 Webpack 的 loader,可以将以下格式编写的Vue组件转换为JavaScript模块。vue-loader提供的部分特性:

  • ES6默认支持;
  • 允许对Vue组建的组成部分使用其他webpack loaders,比如对<style>使用SASS和对<template>使用Jade;
  • .vue文件中允许自定义节点,然后使用自定义的loader处理它们;
  • <style><template>中的静态资源当作模块来对待,并使用 Webpack loaders 进行处理;
  • 对每个组件模拟出 CSS 作用域;
  • 支持开发期组件的热重载;

在webpack中,所有的预处理器需要匹配对应的loader。vue-loader允许使用其他Webpack loaders处理Vue组件的某一部分。它会根据lang属性自动判断出要使用的loaders。

上一节提到的extract-text-webpack-plugin提取css,vue中style标签之间的样式提取的办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader' // <- 这是vue-loader的依赖,所以如果使用npm3,则不需要显式安装
})
}
}
}
]
},
plugins: [
new ExtractTextPlugin("app.css")
]
}

pug 模板

pug 其实就是jade。

vue-loader里对于模板的处理方式略有不同,因为大多数webpack模板处理器(如pug-loader)会返回模板处理函数,而不是变异的HTML字符串,使用原始的 pug 替代 pug-loader:

1
npm install pug --save-dev

使用:

1
2
3
4
<template lang="pug">
div
h1 Hello world!
</template>

如果使用 vue-loader@<8.2.0,还需要安装template-html-loader

PostCSS

安装vue-loader时默认安装了postcss,由vue-loader处理的 CSS 输出,都是通过PostCSS进行作用域重写,还可以为PostCSS 添加自定义插件,例如autoprefixer或者CSSNext

在webpack中使用postcss,需要下载postcss-loader:

1
npm install --save-dev postcss-loader

cssnext

cssnext可以将最新的CSS语法转换为兼容性更强的CSS,所以不必等待各种浏览器的兼容。

安装:

1
npm install --save-dev postcss-cssnext

[!!!这一部分配置值得商榷]

postcss.config.js:

1
2
3
4
5
module.exports = {
plugins: [
require('postcss-cssnext')
]
}


webpack.config.js:

1
2
3
4
5
6
7
8
9
10
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
}
}

cssnext 依赖了autoprefixer,所以我们无需显式下载autoprefixer。更多关于postcss的插件可以看这里:postcss plugins

在这一部分回顾了有关vue的webpack知识:

1
2
3
4
5
6
vue
vue-loader //将符合格式要求的Vue组件转换为JavaScript模块
vue-template-compiler //vue-loader依赖于它,将<template>中的静态资源作为模块使用
pug //模板处理
postcss-loader //vue-loader处理的css输出,都是由它进行作用域重写
postcss-cssnext // PostCss的插件之一,可以处理CSS书写的浏览器兼容问题

webpack2 开启eslint校验

利用ESlint规范代码,ESlint和webpack继承,在babel编译代码开始前,进行代码规范检测。这里使用的是javascript-style-standard风格的校验。

主要依赖几个包:

1
2
3
4
5
6
7
eslint —— 基础包
eslint-loader —— webpack loader
eslint-plugin-html —— 提取并检验你的 .vue 文件中的 JavaScript
eslint-friendly-formatter (生成的报告格式)

# javascript-style-standard 依赖的包
eslint-config-standard (和👇2个包都是javascript-style-standard风格指南需要的包)