webpack

文章目录

webpack4

注意:由于国内的那个 webpack 中文网一直没有更新,它上面的东西还是 webpack3 的,已经太老了,如果想要看文档的话,去官方 webpack.js.org

环境

webpack 基于 node 环境,node 是用 C++ 开发的,不是 JS!并且它使用了 V8 引擎(谷歌出品,这也是谷歌快的原因)。V8 引擎快的原因是,可以直接把 js 代码转换成 二进制码(电脑真正能识别的代码)。正常流程是 js -> 字节码 -> 浏览器,V8 引擎是 js -> 二进制码 -> 浏览器,显然二进制比字节码更容易解析,速度更快。而 node 核心是 V8 引擎,这也是 node 能执行 js 的原因(原本 js 只能在浏览器执行),因此对 js 文件来说,就有了一个底层支撑。

起步

webpack4 与之前版本有较大不同,它已经不支持 webpack 入口文件 出口文件 这样的代码了,即 webpack ./src/index.js ./dist/bundle.js 不能用了,它会报错,提示说 mode 没有指定,可以指定值为 production 或 development,不指定默认为 production,需要到 webpack.config.js 配置文件中指定。同时还有一个报错,没有找到模块,不能被解析(resolve)。 webpack4 以后 webpack-cli 被单独抽离出来了,需要下载,它是必备的,安装一下 npm install --save-dev webpack-cli

下载 webpack 指定版本代码 npm install -D [email protected] ,这是下载 3.6 版本的,如果只输入 3,会下载 webpack3 的最新版本

因此首先需要创建一个 package.json 配置文件,它是 npm 的包管理文件,可以手动创建,也可以在命令行输入 npm init ,默认配置的话一路回车就会自动创建一个配置文件。 npm init -y 也行,相当于帮你按了一路回车。

package.json 中的 script 是调用项目下的 node_modules/.bin 里的同名文件。

一些踩坑记录

  • s的问题: webpack.config.js 中, 输出写的是 module.exports 而不是 module.export!!! 写错了也会打包,就是默认配置,入口时 src 下的 index.js,输出到 dist/main.js。同样的,rules、options、presets、extensions、alias、plugins 的 s 都不要忘记加了!
  • 新版中,如果没有指定 mode,默认指定为 production 生产模式,将会压缩代码,同时会给一个警告,可选值为 productiondevelopment 开发模式。
  • 配置项中的 entry 路径,是相对于项目根目录的,而不是根据配置文件所在的位置。
  • webpack.config.js 中, entry 的路径必须写成相对路径,output 的 path 的路径必须写成绝对路径(C://xxx 这种从磁盘开始的),也因此需要引入 webpack 的 path 模块,它提供了定位到当前文件夹的绝对路径的功能, path.resolve(__dirname, 'dist') dist 是指定的文件夹名,前面的 __dirname 是当前文件夹的绝对路径名,本来就可以获取到这个变量,而不是因为 path 模块导入的,path.resolve 用于拼接前后两个路径,整合成一个绝对路径
  • js 文件中,想要引入另外的文件,必须写成相对路径,如 import './hello.js',不能写成 import 'hello.js'import '/src/hello.js',会直接报错。
  • 老版中可能需要在 package.json 中的 script 设置 build: webpack --config webpack.config.js 才能使用我们自建的 webpack.config.js 文件中的配置项,现在可以直接写成 build: webpack,只要 JSON 文件同级目录下有 webpack.config.js 文件,就默认使用该文件了,同时一旦使用这个配置文件运行过一次,当前的配置项就会被缓存,当作以后的默认项,此时即使把这个文件删除,还是会有效果。
  • 直接在命令行中输入 webpack(只要是在终端中输入命令,都是在全局),是使用全局安装的 webpack 进行打包,这时如果项目中的 webpack 和全局的 webpack 版本不一致,可能会报错,因此需要在项目中安装 webpack,一般不使用全局,如果在 package.json 中配置了 script,像这样 build: webpack,之后输入 npm run build,这时候调用的是项目中的 webpack
  • 开发时依赖 --save-dev,比如 webpack,项目上线的时候是没用的。会被记录到 package.json 中的 devDependencies 属性下。
    运行时依赖 --save,比如 Vue,项目上线时也需要使用它。会被记录到 package.json 中的 dependencies 属性下。
  • webpack 从主入口文件进入,只要发现里面有 require 或 import 这些导入模块的语句,就去导入该模块,如果没写就不会导入,即使已经安装了。并且如果模块中还有导入其他模块,也会导入这些模块。

初始配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path')

module.exports = {
mode: 'development', // 值可以是 'development' 或 'production', 默认 production,会将 js文件压缩。
entry: './src/home.js', // 入口,可以是字符串、数组、对象
output: { // 出口, 通常是一个对象,至少包含了 filename 和 path 两个重要属性
path: path.resolve(__dirname, 'dist'),
filename: 'inde11x.js',
publicPath: 'dist/' // 加上之后,仅仅是 index.html 中涉及到 url 的地方,前面都会加上 dist/ 这个地址。 如 图片会从 url('img.jpg') 变成 url('dist/img.jpg'),注意只是 index.html , 在项目打包后, 把 dist 文件夹给后台的时候,需要配置它, 根据后台把你文件放到相对他根路径的位置, 来决定这里写什么。因为我们默认只是放到 / 这个根路径下,后台可能会把我们打包后的文件夹丢到服务器的 /pages/admin 这样的路径下,那我们这里就需要改成 /pages/admin,然后重新打包发一份给后台
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
}
]
}
}

loader 部分

图片处理

图片处理,可以用 url-loaderfile-loader,可以设置 limit, 图片大小小于某个值的时候使用 url-loader,否则使用 file-loaderurl-loader 会把图片编译成 base64 字符串形式,而file-loader 会重新生成一个同样的图片(默认名称是 32位哈希值,目的是为了防止重名)并放到 dist 目录下。 url-loaderfile-loader 的封装,不要设置了 url-loader,下面又设置了 file-loader,反过来同理,不会报错,但图片不会显示。 use 是数组,当只有一个对象时,可以写成对象。

自定义图片路径名字时,记得遵循格式。前面可以加上文件夹名,表示想把图片放置到 dist 的哪里,默认就在 dist 目录下, [] 里是变量,name 是原文件名, hash 是 32位哈希值,可以用 :Num 来决定选取多少位哈希值(还是得加上哈希值,防止项目变大的时候,前面的文件因重名而被替换), ext 是 extention(扩展名)的意思,表示原文件的后缀名。

1
2
3
4
5
6
7
8
9
10
11
12
{
test: /\.(gif|png|jpg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 6144, // 图片小于 6KB 时,使用 url-loader, 否则使用 file-loader
name: 'img/[name].[hash:8].[ext]' // 图片路径变为 img/name.xxxxxxxx.png
}
}
]
}

babel

安装 babel 转化 ES6 代码的时候,需要先安装 loader, npm install babel-loader babel-core babel-preset-env(这条代码很有可能出现问题,就是下面这种), babel-loader 是加载用的,babel-core 和安装 less-loader 时需要同时安装 less 挺像的,装了才能编译, babel-preset-env 是要转化的版本,老版中 -env 需要配置 .babelrc 文件才能使用。

老版 babel 中,我们可以直接指定 babel-preset-es2015 这样,就不需要配置 .babelrc 文件了,新版中 babel-preset-latest、babel-preset-es2015(@babel/preset-es2015) 已被废弃,需使用 @babel/preset-env 代替。npm 下载包的时候,没有 @ 表示非作用域包, @ 表示作用域包, @ 后面跟的是用户名或组织名,表示该用户或该组织发布的,因为没有 @ 时,这个包名可能被人注册了,无法再用了,所以加上 @,保证不会被占用,如 vue-cli (这种没有作用域的包可以是任何人创建的,你也可以创建个 vue-abcd 的包然后发布上去,当然这里的这个是官方的)和 @vue/cli,这里两个都是官方的,但因为 vue-cli 被注册了,那就只能用 @vue/cli 来作为新的 vue-cli 的包了。如果这个用户或组织不存在,或者它并没有发布什么内容, npm 就会报错。

一般 npm 一个什么包的时候,默认会在包后面加上 @lastest,表示下载最新发表的包,如 npm i vue-template-compiler 就相当于 npm i vue-template-compiler@lastest

1
2
3
4
5
6
7
8
9
10
{
test: /\.js$/,
exclude: '/(node_modules|bower_components)/', // 排除这两个文件夹
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'] //编译成下载的 @babel/preset-env
}
}
}

Vue

安装 Vue

Vue 发布时,会发布两种版本,一种是 runtime-only,一种是 runtime-compiler。

  • runtime-only ,字面意思,只有运行功能,运行时代码中不可以有任何的 template(Vue 实例挂载的 #app )也会被当做 template。它不包含对 template 编译的代码。默认使用这个版本,如果要修改,需要到 webpack.json 去配置 resolve
  • runtime-compiler,字面意思,运行和编译功能,代码中可以有 template,compiler 用于编译 template
别名 alias
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
// ...
output: { /* ... */ },
module: { /* ... */ },
resolve: {
/*
alias 别名的意思,git 中也有类似的操作
比如 git commit -m "提交" 太长了,我们可以给它设置别名,改成 git c "提交"
格式是 git config --global alias.别名 "命令全称"
因此写成 git config --global alias.c "commit -m"

在这里的 alias 作用也类似,在 js 文件中,如果找到 import 'vue',就加载 node_modules/vue/dist/vue.esm.js,也就是 runtime-compiler 版本
默认加载的是 node_modules/vue/dist/vue.runtime.js,也就是 runtime-only 版本
*/
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
// 一般我们 npm install 某个插件,拿到的都是 .esm.js 版本,这是模块化(ECMAScript Module)的版本。去该插件的 github 地址里的 dist 里查看,但 vue 默认用的不是 .esm.js
处理 .vue 文件

同样的,也需要安装 loader,不过 loader 只是加载用的,并不能编译,所以还需要一个模板编译器,所以命令行输入 npm i vue-loader vue-template-compiler -D 安装。

1
2
3
4
5
// 同样的,在 use 里加上
{
test: /\.vue$/,
use: ['vue-loader']
}

在旧版的 vue-loader (低于 14.0.0)情况下,这时候已经能运行 .vue 文件了,但在 14 版本以后,将会报错 vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.,提示我们需要加上插件,那么就加一下,或者直接把版本降级,就不用加入插件了。

1
2
3
4
5
6
7
8
9
10
11
const VueLoaderPlugin = require('vue-loader/lib/plugin')


module.exports = {
// ...
module: { /* ... */ },
resolve: { /* ... */ },
plugins: [
new VueLoaderPlugin()
]
}
省略后缀名

此时应该就能顺利运行 .vue 文件了,但 import 的 .vue 文件的后缀无法省略,加上以下代码后就可以省略了。默认是只能省略 .js 后缀的。但是记得,如果没有同名文件的话,就可以省略后缀,但如果有同名文件,就不能省略,否则后写的将会不生效。如 当目录下同时存在 index.css 和 index.less 文件时,且省略了后缀,变成 import 'index'; import 'index';,那么 extensions 后写的 '.less' 将不会生效,引入的两次 index 都会变成 index.css.

1
2
3
4
5
6
7
8
// ...
resolve: {
extensions: ['.vue', '.js', '.css', '.less'], // 把想省的都写这。
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
// ...

Plugins

Plugins 可以扩展 webpack 的功能。

清除dist目录的插件

clean-webpack-plugin ,在每次打包前清除下dist文件夹。

安装

npm install clean-webpack-plugin -D

使用
1
2
3
4
5
6
7
const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 解构赋值

// ...
plugins: [
new CleanWebpackPlugin()
]
// ...

分离 css 文件的 Plugin

mini-css-extract-plugin ,将会把和 bundle.js(打包后的 js 文件,写到一起可以减少请求)整合在一起的 css 单独抽离出来(方便检查)。

安装

npm install mini-css-extract-plugin -D

使用

其实就是把原来 style-loader 的位置改成了这个插件,但和其他插件不同的是,在 plugins 里 new 了之后还需要在 module.rules 中使用。

老版中应该使用 extract-text-webpack-plugin ,这里不再举例,它适用于 webpack3 而不适用于 webpack4,它也被使用在 vue-cli3 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// ...
module: {
rules: [
{ // 其实就是把 style-loader 改成了这个插件
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' // css 文件中的引用路径前面加上 ../,注意,这也是仅作用于 css 文件的,和外面那个 publicPath 只影响 index.html 一样,它们不会互相干扰。
}
},
'css-loader' // 可以用对象形式也可以用字符串形式,还可以混用。
]
}
]
},
plugins: [ // 还需要引入插件
new MiniCssExtractPlugin({
// 指定生成路径为 dist/css/name.hash.css name 为自己的 css 文件名 hash 为运行时系统自动生成的唯一 hash 值, 所以即使定义了同名的css, 打包也不会因为重名而失效,拓展名固定为 .css,如果用 [ext],可能会变成 .less .scss .styl 等。
filename: 'css/[name].[hash:8].css'
})
]
// ...

添加版权的 Plugin

BannerPlugin 是 webpack 自带的插件,可以在打包后的 js 文件头部加上点想写的东西。

讲故事:比如 vue 前面就有一串这样的版权信息,使用 MIT 开源协议,这个协议对使用者的束缚更小,React 的协议也从 BSD + 专利条款改为更受欢迎的 MIT 许可证,原来 React 的协议允许 Facebook 随时收回 React 的使用权,随时可以告你侵权,这是一种防御性质的 license,但由于这个开源协议存在的原因,许多大公司开始弃用 React,最后 Facebook 做出让步,改为 MIT。当时使用 React 的国外大型公司有 Apache基金会、Wordpress,国内的百度等公司,那时候的这些公司都宣布弃用 React。如果希望别人使用你的代码时必须声明这是你的代码,可以使用 Apache License,开放性比较高的就是 MIT。

1
2
3
4
5
/*!
* Vue.js v2.6.11
* (c) 2014-2019 Evan You
* Released under the MIT License.
*/

使用:

1
2
3
4
5
6
7
8
const webpack = require('webpack')

// ...
resolve:{ /* ... */ },
plugins: [
new webpack.BannerPlugin('Vue.js v2.6.11' + '\n' + '(c) 2014-2019 Evan You' + '\n' + 'Released under the MIT License.')
]
// ...

在 dist 目录下添加 html 文件

htmlWebpackPlugin 作用是每次 build 时,在 dist 目录下新建一个 html 文件,并自动引入打包后的 js 文件。

由于不是 webpack 自带的插件,需要安装: npm i html-webpack-plugin -D

使用:方法接受一个对象,用于配置。当使用了这个插件之后,记得把 publicPath 给去掉,否则引入的 js 文件路径前面将会加上设置的 publicPath,就会找不到文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const HtmlWebpackPlugin = require('html-webpack-plugin')

// ...
output: {
// ...
// , publicPath: 'dist/' // 这个得去掉或者注释掉。
},
resolve:{ /* ... */ },
plugins: [
new HtmlWebpackPlugin({
// 允许接受一个 html 模板,来控制生成的 html 里的基本内容。否则将生成一个空的 html (只有基本结构,就一个 ! 敲出来的那种)并导入 js。
template: 'index.html'
})
]
// ...

压缩代码

UglifyJsPlugin 作用如英文翻译,丑化插件,移除代码里的未使用的内容和空格。

安装

npm install uglifyjs-webpack-plugin --save-dev

使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

// ...
plugins: [
new UglifyJsPlugin() // 通用写法
/* webpack3 写法
new webpack.optimize.UglifyJsPlugin() // 这种写法在 webpack3 内置了,在这个版本下,可以不用下载,但 webpack4 中移除了。
*/
]
// ...

// ...
// webpack4+ 写法,在 optimization 中设置 minimize,但 webpack4+ 可以直接把 mode 设置为 production ,或者 JSON 文件中配置 "build": "webpack --mode production", 自动压缩代码,也是修改了下面的配置项。
plugins: [],
optimization: {
minimize: true
}
// 另外可以指定压缩的插件,可以用自己下载的插件来压缩。 webpack4 中只有自己的 minimize,没有 UglifyJsPlugin 了。
/* optimization: {
minimize: true, // 当然这一行还是要加,默认为 false,不打包。
minimizer: [ // 使用自己的打包插件
new UglifyJsPlugin({
uglifyOptions: {
compress: false,
}
})
]
}
*/
// ...

本地服务器

webpack-dev-server ,用于搭建本地服务器,基于 nodejs,内部使用 express 框架,可以让它服务于 dist 文件夹,实时监听这个文件夹的改变,一旦改变,会对所有代码重新进行编译。它可以把读取的文件都存放到内存中,测试时读取文件都基于内存,内存读取速度远大于磁盘。这种情况,也就是我们说的热更新(此时相当于 vue-cli 中的 npm run serve),只有当 npm run build 之后,再把内存的文件放到磁盘中(也就是放到 dist 文件夹中,本来 dist 文件夹中是没有东西的)。

配置
1
2
3
4
5
6
7
8
9
10
// ...
optimization: { /* ... */ },
devServer: {
contentBase: './dist', // 监听文件夹
inline: true, // 页面刷新模式 inline 和 Iframe(构建消息在浏览器页面头部显示,会自动生成一个 头部提示框,浏览器访问是 localhost:8080/webpack-dev-server/index.html)。默认 inline(true),构建消息在浏览器控制台显示,浏览器访问路径是 localhost:8080/index.html
port: 8080, // 指定端口 默认 8080
historyApiFallback: true // 是否使用 h5 路由
// , open: true // 或者协商这个,下面的 --open 就可以省略了,也会自动打开浏览器。
}
// ...

安装完后在命令行中输入 ./node_modules/.bin/webpack-dev-server --open,PowerShell 中可能需要管理员运行,编译完成即可自动打开网页(--open 在编译完后自动打开该端口),并监听该端口。

也可以在 package.json 中的 script 配置如下,之后 npm run dev 即可达到和上面的命令一样的效果。

1
2
3
"scripts": {
"dev": "webpack-dev-server --open"
}

拆分 webpack.config.js 配置文件

拆分

由于生产模式和开发模式需要的配置项不同,不希望混用,但又存在较多的公共样式,所以需要拆分。

把原来的 webpack.config.js 拆分成 base.config.js(公共) 、prod.config.js(生产)、dev.config.js(开发) 三个配置项。此时,抽离原配置文件的公共项到 base.config.js 中,排除非公共项,如开发不需要 UglifyJsPlugin,生产不需要 devServer。prod.config.js 中只写生产需要,dev.config.js 只写开发需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// base.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
mode: 'development',
entry: './src/home.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
// ,
// publicPath: 'dist/'
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } }, 'css-loader']

// use: ['style-loader', 'css-loader']
// use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
},
{
test: /\.less$/,
use: [ { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } }, 'css-loader', 'less-loader']
// use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(gif|png|jpg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 6000,
name: 'img/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude: '/(node_modules|bower_components)/',
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
extensions: ['.vue', '.js', '.css', '.less'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new VueLoaderPlugin(),
new webpack.BannerPlugin(
'Vue.js v2.6.11' + '\n' + '(c) 2014-2019 Evan You' + '\n' + 'Released under the MIT License.'
),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:8].css'
}),
new CleanWebpackPlugin()
]
}

// prod.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
optimization: {
minimize: true,
minimizer: [new UglifyJsPlugin()]
}
})

// dev.config.js
module.exports = {
devServer: {
contentBase: './dist',
inline: false,
port: 8080,
historyApiFallback: true
}
})

此时,分离之后,就需要整合。需要下载 merge 工具。

安装

npm install webpack-merge -D

使用

在两种模式的头部引入公共配置和 merge,之后调用 merge 函数。之后就写成这样了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// dev.config.js
const { merge } = require('webpack-merge')
const baseConfig = require('./base.config.js')

module.exports = merge(baseConfig, {
devServer: {
contentBase: './dist',
inline: true,
port: 8080,
historyApiFallback: true
}
})

// prod.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const { merge } = require('webpack-merge')
const baseConfig = require('./base.config.js')

module.exports = merge(baseConfig, {
optimization: {
minimize: true,
minimizer: [new UglifyJsPlugin()]
}
})

/*
新版 webpack-merge 与老版写法不一致,这是因为新版源码中向外暴露对象的写法与老版不一致。
新版文件中暴露的接口是这样写的: export { merge, mergeWithCustomize, unique, customizeArray, customizeObject }
所以可以使用解构赋值来获取 merge
*/

// 老版 webpack-merge 写法
const webpackMerge = require('webpack-merge')
module.exports = webpackMerge(baseConfig, {
// ...
}
})

这样就分离完成了。

当然,我们一般不会把这三个配置项都放下根目录下,一般做法是在根目录下新建一个 build 文件夹,并把这三个配置项都放进去,并移除旧的在根目录下 的 webpack.config.js。

由于我们把配置文件位置移动了,因此,base.config.js 的打包路径也需要改动,否则 dist 会打包到 build 目录下。

1
2
3
4
5
6
7
8
9
10
// base.config.js
module.exports = {
// ...
entry: './src/home.js', // 入口是不需要修改的,因为它本身是相对于项目根目录的,而不是配置文件位置
output: {
path: path.resolve(__dirname, '../dist'), // 由于移到了 build 里面而不是根目录,需要返回上一级。
filename: 'index.js'
}
// ...
}

接着需要把 package.json 中的配置修改一下。

1
2
3
4
5
6
7
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --config ./build/dev.config.js --open"
},
}

--config 表示变量 config 配置项,使用它后面空格之后的内容。这里表示 config 配置文件使用 ./build/prod.config.js )生产)或 ./build/dev.config.js(开发)。

结束。

附上原来的未拆分版本

原完整未拆分版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// webpack.config.js
// 未使用 clean-webpack-plugin 和 mini-css-extract-plugin

const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
mode: 'development', // 值可以是 'development' 或 'production', 默认 production,会将 js文件压缩。
entry: './src/home.js', // 入口,可以是字符串、数组、对象
output: { // 出口, 通常是一个对象,至少包含了 filename 和 path 两个重要属性
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
// ,
// publicPath: 'dist/' // 加上之后,仅仅是 index.html 中涉及到 url 的地方,前面都会加上 dist/ 这个地址。 如 图片会从 url('img.jpg') 变成 url('dist/img.jpg'),注意只是 index.html
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
// use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(gif|png|jpg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 6000,
name: 'img/[name].[hash:8].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude: '/(node_modules|bower_components)/',
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
extensions: ['.vue', '.js', '.css', '.less'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new VueLoaderPlugin(),
new webpack.BannerPlugin(
'Vue.js v2.6.11' + '\n' + '(c) 2014-2019 Evan You' + '\n' + 'Released under the MIT License.'
),
new HtmlWebpackPlugin({
template: 'index.html'
})
],
optimization: {
minimize: true,
minimizer: [new UglifyJsPlugin()]
},
devServer: {
contentBase: './dist',
inline: true,
port: 8080,
historyApiFallback: true
}
}

遗漏追加

proxy

同源策略是浏览器的,所以 node 不受影响。

开发环境通过配置 devServer 的 proxy,实现跨域,它无法应用于线上环境。因为该技术只是在 webpack 打包阶段在本地临时生成了 node server,来实现类似 nginx 的 proxy_pass 的反向代理效果。  proxy 工作原理实质上是利用 http-proxy-middleware 这个 http 代理中间件,实现请求转发给其他服务器。

1
2
3
4
5
6
7
8
9
var proxy = require('http-proxy-middleware');

var apiProxy = proxy('/api', {target: 'http://www.example.org'});
// \____/ \_____________________________/
// | |
// context options
// content:用于定义哪些请求需要被目标主机代理
// option.target:目标主机(协议+主机名)
// 也可以简写 var apiProxy = proxy('http://www.example.org/api')

线上环境可以通过配置 Ngix 来解决跨域,不过这一般是后端处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.export = {
// ...
devServer: {
// ...
proxy: {
// 在后台没配置 cors 的时候,可以用 proxy 来解决跨域
// 将以 /admin 开头的 url 请求都配置到 target 的网站去
// 此时需要把 axios 的 baseURL 关了,url 只发送 /admin, 这样就会被 webpack 代理了。
// 此时相当于 在 http://www.xxxx.com 向 http://www.xxxx.com/admin/login。请求的地址就变成了 localhost:9528/admin/login。
'^\\/admin': { // 这是正则字符串, 字符串中 \\ 会变成 \
target: 'http://www.xxxx.com',
changeOrigin: true, // 是否跨域,跨域必须配置
pathRewrite: {
// 重写 /admin 变成 /dloux
// 如 http://www.xxxx.com/admin/login 变成 http://www.xxxx.com/dloux/login
'/admin': '/dloux'
}
}
}
}
}

题外:

webpack 打包后的 js 文件

可以看到整个文件的格式是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(function(modules) {
// ...
return __webpack_require__(__webpack_require__.s = 0)
})([/* ... */])

// 可以看到,它是一个立即执行的匿名函数。 在它里面,定义了一个叫 __webpack_require_ 的方法,传入下面的数组参数的 id,即传入一个个模块的 id, 这个 id 是 0, 1, 2 这样的顺序,从 0 开始。按顺序执行
function __webpack_require_(moduleId) {
// ... 执行代码
}

/*
上面立即执行的匿名函数最后一句是
return __webpack_require__(__webpack_require__.s = 0)
相当于下面这两行,先对 .s 赋值为 0,再获取 .s 的值,而此时值为 0。关于这个写法可以看看 《你不知道的 JS 第一章》
__webpack_require__.s = 0
return __webpack_require__(__webpack_require__.s)
*/


// 传入的数组参数,里面是一个个模块,都是我们自己写的 js 代码,被包装成一个个函数
[
(function (/* ... */) {
// ... 一堆配置代码
console.log('我的代码1')

// ... 它的代码,这里一般也是 console.log 什么东西
}),
(function (/* ... */) {
// ... 一堆配置代码
console.log('我的代码2')

// ... 它的代码,这里一般也是 console.log 什么东西
})
]

Vue-cli 的 runtime-only 和 runtime-compiler

我们用 vue-cli 构建项目时,对 vue 会有两种选择 runtime-onlyruntime-compiler,一种能编译 template,一种不能,所以代码体积小了6KB ( 30% ).

runtime-compiler 流程是 template -> ast -> render() -> vdom -> UI

1
2
3
4
5
6
7
8
9
10
import App from './App.vue'
new Vue({
el: '#app',
components: {
App
},
template: '<App />' // 这个版本的 vue 文件内部有编译 template 功能,允许 template 属性
})

// 这个版本也允许使用下面的 render 函数,两者可以写的完全一样,也就是说,下面的版本是该版本的子集。

runtime-only 流程是 render() -> vdom -> UI,省去的过程在运行前由 vue-template-compiler 执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import App from './App.vue'
new Vue({
el: '#app',
render: function (createElement) { // render 是一个函数,到时候会传进来一个 createElement 函数,下面说说这个函数,这个函数可以自定义渲染的内容(即 #app 被替换成什么)
return createElement(App) // 返回一个 VNode 即虚拟 DOM,直接交给 Vue 去渲染
}
})
/*
我们发现我们不写 render 也能正常运行 Vue,这是因为Vue 内部写了个 if 判断来决定是否使用 render
if (typeof this._options.render !== 'function') {
this.render = this.createRenderFn() // 调用另一个渲染函数
}
*/

// 这个版本的 vue 文件内部没有编译 template 功能,编译 template 的任务交给了 vue-template-compiler,即把解析 template 的过程放到了运行前,而不是上面那种的运行时,将返回一个 render 函数 h,已经把 template 解析完毕。
// 引入 App 的时候,App 已经是个解析过后的对象了,它上面有 render 函数,而不是一大堆的 template + js + css

默认传入的 createElement (即 h)是 Vue 里的创造 DOM 元素的方法。用法如下,参数 1 为标签,参数 2 为一个对象,表示该元素上的属性,参数 3 是一个数组,表示被该标签包裹的内容。如果参数 2 是数组,则代表该标签未定义属性,数组内容就是被标签包裹的内容。

1
2
3
4
5
new Vue{
render(createElement) {
return createElement('div', { class:"class1" }, ['这是内容,这是生成真正 DOM 的函数,它会被挂载道页面中', createElement('span'),['这是子元素内容,如果第二项是数组,就会自动变成内容插入。']])
}
}

另外 vue-cli 3+ 之后会写成这样,$mount 和 el: '#app' 几乎没有区别,在源码中, el: '#app' 会被转化为 $mount('#app')

1
2
3
new Vue{
render: h => h(App)
}.$mount('#app')

vue-cli 的配置文件位置 .vuerc

一般这些全局配置文件都在 C:\Users\Administrator,在这里也可以找到 git 的配置文件和 .npmrc , 关于 rc 的后缀名源自 linux,表示是在命令行(终端)运行的, run command

在 .vuerc 中,可以修改创建的 vue 模板。就是 vue create projectname 创建后保存的模板可以在这里删除修改。

vue-cli3+ 的配置文件

vue-cli3+ 之后,作者希望 vue 可以 0 配置上手,因此创建项目后,根目录不再能看到配置文件(vue-cli2 中可以看到一堆),这是因为 vue-cli3+ 之后,配置文件都交给 @vue-cli-service 托管了,在 npm i @vue-cli 的时候会一起下载,也因此 package.json 中的 scripts 变成了 ,都从 vue-cli-sevice 的配置文件中获取。

如果想要查看,配置文件的入口文件在 /node_modules/@vue/cli-service/webpack.config.js,可以根据它的导入来确认真正的配置文件。记得是找 @vue 文件夹。

另一种方法是通过 vue ui 可视化面板中的配置 选项中查看。

1
2
3
4
5
6
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
}
}

如果想要修改配置文件,可以在项目根目录下创建 vue.config,js 文件(必须是这个名字),用 module.exports = {} 来导出配置,到时候就会和默认配置合并。

输入 webpack 之后干了什么

找到 node_modules/webpack/package.json,可以看到

1
2
3
"bin": {
"webpack": "bin/webpack.js"
}

就是执行了这个 JSON 文件同级下的 bin 文件夹内的 webpack.js

别名 alias 在 vue-cli 中的使用

可以在 node_modules/@vue/cli-service/lib/config/base.js 中看到,大概在 39-59 行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// vue.config.js (这是 vue-cli3+ 之后的自定义配置文件,将会与默认配置文件混合,并且覆盖它)

module.exports = {
// configureWebpack 表示告诉 Vue 要配置 webpack,必须卸载这个对象里面才能配置 webpack
configureWebpack: {
// ...
resolve: {
/*
alias 是别名的意思,在这里配置了之后,import 就可以改写了
原来在 js 中 import 的写法
assets: '../../assets/xxx' 或 '@/assets/xxx'
components: '../../components/xxx' 或 '@/components/xxx'
views: '../../views/xxx' 或 '@/views/xxx'
增加别名之后在 js 中 import 的写法
assets: 'assets/xxx'
components: 'components/xxx'
views: 'views/xxx'
增加别名之后在 html 中,比如在 src 属性上写访问静态资源,前面需要加上 ~
如 <img src="~assets/img/img1.png">
*/
alias: {
'@': resolve('src'), // 写了这行之后就可以用 @ 表示 src 路径了,自己的 webpack 中也能这样,但不知道是不是 webpack4+ 才支持。
'assets': '@/assets', // 在 vue-cli3+ 中,可以用上面定义的 @ 代表 src
'components': '@/components', // 但在 vue-cli2 中不行,还是得写成 src/
'views': '@/views' // 比如这个 views,就得写成 resolve('src/views')
}
}
}
}

.exe 是 execute(执行的缩写),所以那些可执行的文件都是 .exe 结尾

分享到:

评论完整模式加载中...如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理