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
生产模式,将会压缩代码,同时会给一个警告,可选值为production
和development
开发模式。 - 配置项中的 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 | const path = require('path') |
loader 部分
图片处理
图片处理,可以用 url-loader
或 file-loader
,可以设置 limit
, 图片大小小于某个值的时候使用 url-loader
,否则使用 file-loader
,url-loader
会把图片编译成 base64 字符串形式,而file-loader
会重新生成一个同样的图片(默认名称是 32位哈希值,目的是为了防止重名)并放到 dist 目录下。 url-loader
是 file-loader
的封装,不要设置了 url-loader
,下面又设置了 file-loader
,反过来同理,不会报错,但图片不会显示。 use
是数组,当只有一个对象时,可以写成对象。
自定义图片路径名字时,记得遵循格式。前面可以加上文件夹名,表示想把图片放置到 dist 的哪里,默认就在 dist 目录下, []
里是变量,name 是原文件名, hash 是 32位哈希值,可以用 :Num 来决定选取多少位哈希值(还是得加上哈希值,防止项目变大的时候,前面的文件因重名而被替换), ext 是 extention(扩展名)的意思,表示原文件的后缀名。
1 | { |
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 | { |
Vue
安装 Vue
Vue 发布时,会发布两种版本,一种是 runtime-only,一种是 runtime-compiler。
runtime-only
,字面意思,只有运行功能,运行时代码中不可以有任何的 template(Vue 实例挂载的 #app )也会被当做 template。它不包含对 template 编译的代码。默认使用这个版本,如果要修改,需要到 webpack.json 去配置 resolveruntime-compiler
,字面意思,运行和编译功能,代码中可以有 template,compiler 用于编译 template
别名 alias
1 | module.exports = { |
处理 .vue 文件
同样的,也需要安装 loader,不过 loader 只是加载用的,并不能编译,所以还需要一个模板编译器,所以命令行输入 npm i vue-loader vue-template-compiler -D
安装。
1 | // 同样的,在 use 里加上 |
在旧版的 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 | const VueLoaderPlugin = require('vue-loader/lib/plugin') |
省略后缀名
此时应该就能顺利运行 .vue 文件了,但 import 的 .vue 文件的后缀无法省略,加上以下代码后就可以省略了。默认是只能省略 .js 后缀的。但是记得,如果没有同名文件的话,就可以省略后缀,但如果有同名文件,就不能省略,否则后写的将会不生效。如 当目录下同时存在 index.css 和 index.less 文件时,且省略了后缀,变成 import 'index'; import 'index';
,那么 extensions 后写的 '.less'
将不会生效,引入的两次 index 都会变成 index.css.
1 | // ... |
Plugins
Plugins 可以扩展 webpack 的功能。
清除dist目录的插件
clean-webpack-plugin
,在每次打包前清除下dist文件夹。
安装
npm install clean-webpack-plugin -D
使用
1 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 解构赋值 |
分离 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 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') |
添加版权的 Plugin
BannerPlugin
是 webpack 自带的插件,可以在打包后的 js 文件头部加上点想写的东西。
讲故事:比如 vue 前面就有一串这样的版权信息,使用 MIT 开源协议,这个协议对使用者的束缚更小,React 的协议也从 BSD + 专利条款改为更受欢迎的 MIT 许可证,原来 React 的协议允许 Facebook 随时收回 React 的使用权,随时可以告你侵权,这是一种防御性质的 license,但由于这个开源协议存在的原因,许多大公司开始弃用 React,最后 Facebook 做出让步,改为 MIT。当时使用 React 的国外大型公司有 Apache基金会、Wordpress,国内的百度等公司,那时候的这些公司都宣布弃用 React。如果希望别人使用你的代码时必须声明这是你的代码,可以使用 Apache License,开放性比较高的就是 MIT。
1 | /*! |
使用:
1 | const webpack = require('webpack') |
在 dist 目录下添加 html 文件
htmlWebpackPlugin
作用是每次 build 时,在 dist 目录下新建一个 html 文件,并自动引入打包后的 js 文件。
由于不是 webpack 自带的插件,需要安装: npm i html-webpack-plugin -D
使用:方法接受一个对象,用于配置。当使用了这个插件之后,记得把 publicPath 给去掉,否则引入的 js 文件路径前面将会加上设置的 publicPath,就会找不到文件。
1 | const HtmlWebpackPlugin = require('html-webpack-plugin') |
压缩代码
UglifyJsPlugin
作用如英文翻译,丑化插件,移除代码里的未使用的内容和空格。
安装
npm install uglifyjs-webpack-plugin --save-dev
使用
1 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') |
本地服务器
webpack-dev-server
,用于搭建本地服务器,基于 nodejs,内部使用 express 框架,可以让它服务于 dist 文件夹,实时监听这个文件夹的改变,一旦改变,会对所有代码重新进行编译。它可以把读取的文件都存放到内存中,测试时读取文件都基于内存,内存读取速度远大于磁盘。这种情况,也就是我们说的热更新(此时相当于 vue-cli 中的 npm run serve),只有当 npm run build 之后,再把内存的文件放到磁盘中(也就是放到 dist 文件夹中,本来 dist 文件夹中是没有东西的)。
配置
1 | // ... |
安装完后在命令行中输入 ./node_modules/.bin/webpack-dev-server --open
,PowerShell 中可能需要管理员运行,编译完成即可自动打开网页(--open
在编译完后自动打开该端口),并监听该端口。
也可以在 package.json 中的 script 配置如下,之后 npm run dev 即可达到和上面的命令一样的效果。
1 | "scripts": { |
拆分 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 | // base.config.js |
此时,分离之后,就需要整合。需要下载 merge 工具。
安装
npm install webpack-merge -D
使用
在两种模式的头部引入公共配置和 merge,之后调用 merge 函数。之后就写成这样了。
1 | // dev.config.js |
这样就分离完成了。
当然,我们一般不会把这三个配置项都放下根目录下,一般做法是在根目录下新建一个 build 文件夹,并把这三个配置项都放进去,并移除旧的在根目录下 的 webpack.config.js。
由于我们把配置文件位置移动了,因此,base.config.js 的打包路径也需要改动,否则 dist 会打包到 build 目录下。
1 | // base.config.js |
接着需要把 package.json 中的配置修改一下。
1 | { |
--config
表示变量 config 配置项,使用它后面空格之后的内容。这里表示 config 配置文件使用 ./build/prod.config.js )生产)或 ./build/dev.config.js(开发)。
结束。
附上原来的未拆分版本
原完整未拆分版本
1 | // webpack.config.js |
遗漏追加
proxy
同源策略是浏览器的,所以 node 不受影响。
开发环境通过配置 devServer 的 proxy,实现跨域,它无法应用于线上环境。因为该技术只是在 webpack 打包阶段在本地临时生成了 node server,来实现类似 nginx 的 proxy_pass
的反向代理效果。 proxy
工作原理实质上是利用 http-proxy-middleware
这个 http 代理中间件,实现请求转发给其他服务器。
1 | var proxy = require('http-proxy-middleware'); |
线上环境可以通过配置 Ngix 来解决跨域,不过这一般是后端处理。
1 | module.export = { |
题外:
webpack 打包后的 js 文件
可以看到整个文件的格式是
1 | (function(modules) { |
Vue-cli 的 runtime-only 和 runtime-compiler
我们用 vue-cli 构建项目时,对 vue 会有两种选择 runtime-only
和 runtime-compiler
,一种能编译 template,一种不能,所以代码体积小了6KB ( 30% ).
runtime-compiler
流程是 template -> ast -> render() -> vdom -> UI
1 | import App from './App.vue' |
runtime-only
流程是 render() -> vdom -> UI,省去的过程在运行前由 vue-template-compiler 执行。
1 | import App from './App.vue' |
默认传入的 createElement
(即 h)是 Vue 里的创造 DOM 元素的方法。用法如下,参数 1 为标签,参数 2 为一个对象,表示该元素上的属性,参数 3 是一个数组,表示被该标签包裹的内容。如果参数 2 是数组,则代表该标签未定义属性,数组内容就是被标签包裹的内容。
1 | new Vue{ |
另外 vue-cli 3+ 之后会写成这样,$mount 和 el: '#app'
几乎没有区别,在源码中, el: '#app'
会被转化为 $mount('#app')
1 | new Vue{ |
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 | { |
如果想要修改配置文件,可以在项目根目录下创建 vue.config,js 文件(必须是这个名字),用 module.exports = {}
来导出配置,到时候就会和默认配置合并。
输入 webpack 之后干了什么
找到 node_modules/webpack/package.json,可以看到
1 | "bin": { |
就是执行了这个 JSON 文件同级下的 bin 文件夹内的 webpack.js
别名 alias 在 vue-cli 中的使用
可以在 node_modules/@vue/cli-service/lib/config/base.js
中看到,大概在 39-59 行。
1 | // vue.config.js (这是 vue-cli3+ 之后的自定义配置文件,将会与默认配置文件混合,并且覆盖它) |