概述,在webpack里面有四个概念: 入口、出口、插件和loader,进行模块化打包,支持CMD、ADM、commonJS、import等模块化操做。css
安装说明:在webpack安装的时候,须要先安装一个全局的webpack,而后在须要的文件夹中安装局部的依赖,webpack webpack-cil脚手架,在webpack3的版本中,它们是集成在一块儿。本次笔记是针对于:webpack4.x以上的版本html
npm install webpack -g //先安装全局的webpack,安装完成后,才能够实验webpack命令,直接打包
npm install webpack webpack-cli -D // 在须要的目录安装webpack
webpack // 在cmd或者vscode中运行这个,就能够打包了,其它的配置,且看后面
复制代码
安装完成后,须要在项目根目录新建一个
webpack-config.js
的配置文件;没有新建这个文件,也能够打包,由于webpack
有默认配置。前端
若是没有安装全局webpack的话,就没法直接使用webpack直接打包,固然,也能够单独是配置脚本,例:vue
scripts: {
dev: webpack --config webpack-config.js
}
复制代码
在4.0之后的版本里面,新增了一个多入口打包命令:webpack app.js app1.ja -o bulid.jsnode
在webpack中有生产模式(production)和开发模式(development),默认为开发设置,用,mode参数去设置模式,例:react
module.exports = {
mode: 'production' // 模式选择,生产和开发两种
};
复制代码
特别说明:须要指定mode属性,否则控制台会报出一个警告信息jquery
入口(entry point),表示 webpack
应该使用哪一个模块;进入入口之后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。webpack
入口能够是单入口和多入口(使用一个数组或者对象表示),在实际操做中,为了便于优化以及首屏加载问题,会更倾向于第三种方式,既对象的形式。ios
可是使用对象写法(
entry
代码分离),会有一些小问题;假设entry
引入了2个js
文件,分别是a.js
和b.js
,可是这个两个文件都引入了同一个依赖,打包之后,就会形成代码重复,很是不利于优化,因此须要使用SplitChunksPlugin
来进行防止重复或者是动态倒入的方法。点击查看这种方式的坑点:代码分离。git
module.exports = {
entry: './app.js' // 写法一
entry: ['./app.js, main.js'] //写法二
entry: {
app: './app.js',
main: './main.js'
} //写法三
};
复制代码
若是使用(output)属性表示,这个属性是用来告诉webpack输出它所建立的内容,默认的目录是dist文件夹,在项目的根目录中,也能够指定一个目录导出。
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js', // 入口
output: { // 出口
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js' // 单文件合并的写法
filename: '[name].js', // 多入口多文件
chunkFilename: [name][contenthash].js, // 防止缓存,只有在更新之后才会生成随机码
publicPath: 'CDN地址' // 设置CDN服务器地址, 理论上是能够直接打包到CDN服务器上,前提是有权限
}
};
复制代码
入口文件有配置的,就走filename
这个属性,若是是其它文件导入的,就走下面这个,既chunkFilename
,在使用的时候,目的就是解决代码更新,而浏览器读取缓存获取不到更新。
在output中,filename属性是用来告诉webpack打包后的文件名称;而path是告诉webpack须要建立的文件夹的位置信息,这个要调用node里面一个模块,既path模块,__dirname是node里面的一个关键字,表示路径,绝对路径
提示:在webpack里面,多个入口若是要合并文件,只能使用数组的方式; 若是须要打包多文件的时,入口须要使用对象,出口使用一个动态的写法,如上代码"多入口文件", [name].js,若是入口是一个对象,出口必定是一个动态写法,不然就会致使打包不正常。
loader 让 webpack 可以去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 能够将全部类型的文件转换为 webpack 可以处理的有效模块,而后你就能够利用 webpack 的打包能力,对它们进行处理。
loader的执行顺序,是从下到上,从右到左的执行顺序,因此在设置loader的时候,顺序不能放错,不然,可能会致使打包异常。
我的理解: loader就至关因而一个翻译器,将咱们写的代码,进行翻译并处理。使用方法,以下:babel为例
注意:在lodaer里面,配置正则(test)的时候,必定不能加引号,不然会提示lodaer错误。
介绍:babe就是将ES6转成ES5的一个编译器,须要安装3个依赖;虽然Babel能够将ES6的语法转成ES5的语法,可是若是是ES6+里面的内置API就没法去转换了,所以安装一个垫片@babel/polyfill,它主要是为了兼容低版本的浏览器,
babel-loader @babel/core // 主要是语法转换
@babel/preset-env // 这个用来指定ES的版本
@babel/polyfill // 全局垫片,用于转换内置API支持ES5的语法
/* 局部垫片须要安装的依赖项 */
@babel/plugin-transform-runtime
@babel/runtime
复制代码
module: {
rules: [
{
test: /\.m?js$/, // 匹配文件的后缀名
exclude: /(node_modules|bower_components)/, // 须要剔除的目录
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'], // 垫片使用
plugins: ['@babel/plugin-proposal-object-rest-spread'] //额外的
}
}
}
]
}
复制代码
关于属性use的操做,若是配置项目不是不少的时候,能够不用use这个属性,它们两个是等价的,直接写,例:
rules: [{
test: /\.m?js$/, // 匹配文件的后缀名
loader: 'babel-loader', // 加载的loader
exclude: /(node_modules|bower_components)/, // 须要剔除的目录
options: { //配置项
presets: ['@babel/preset-env'], // 默认写法,不使用配置的写法
presets: [["@babel/preset-env", { // 第二种写法,配置一个babel/polyfill垫片
useBuiltIns: "usage", // 按需加载 entry | usage
targets: {
chrome: "68" // 浏览器目标,也能够跟随一个浏览器的市场份额
}
}]
]
}
}]
复制代码
关于@babel/polyfill
的使用:先安装,须要在被打包的文件中引入 @babel/polyfill
这个模块,默认是会打包全部的API内置模块,所以须要一个按需加载,写法:useBuildIns: 'usage'
。
默认会报出一个警告,解决方案:若是配置了usage
的话,就不须要在被打包的文件种引入@babel/polyfill
,若是须要引入的话:则须要用entry
替换(局部方式,按照文件需求)
关于局部垫片的使用说明:若是是写的一个插件,就不能使用全局垫片,使用局部垫片须要有两个依赖@babel/plugin-transform-runtime
和@babel/runtime
。
在项目根目录须要新建一个单文件,文件名:.babelrc
,格式是一个标准的JSON,因此须要使用标准的JSON格式,下面已局部垫片配置为例:
{
"presets":["@babel/preset-env"],
"plugins": [["@babel/plugin-transform-runtime", {
"absoluteRuntime": false,
"corejs": 2, // corjs须要改为2,不改的话会形成打包异常,官方默认是false,可选数字和布尔
"helpers": true,
"regenerator": true,
"useESModules": false
}]
]
}
/* ================ 正常模式下 使用文件名的方式 配置 ========================*/
{
"presets": [["@babel/preset-env", {
"useBuiltIns": "usage",
"targets": {
"chrome": "68" // 浏览器目标,也能够跟随一个浏览器的市场份额,也能够是:last 100 versions
}
}]
]
}
复制代码
关于异常的处理:corejs
官方的默认值是false,可是这样会有异常,结果就是不会转换;所以在corejs
的属性后面要跟上一个数字2,可是这样,仍是不行,所以须要下载一个@babel/runtime-corejs2
依赖项,用来改变以前的@babel/runtime
注意:在设置presets属性的时候,若是须要配置,第二个对象是放在嵌套数组里面,如:
[['xxx',{} ]]
,而且,不能够在在webpack里面设置了之后,再去单独的.babelrc
里面去设置,根据试验证实:会报错。
想要使用一个插件,你只须要
require()
它,而后把它添加到plugins
数组中。多数插件能够经过选项(option)自定义。你也能够在一个配置文件中由于不一样目的而屡次使用同一个插件,这时须要经过使用new
操做符来建立它。
这是官方的说法,我本身的理解就是,给webpack扩展一个功能,这个功能极其强大,能够用来处理各类各样的任务,插件的使用方法以下:
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 经过 npm 安装的模块
const webpack = require('webpack'); // 用于访问内置插件
const path = require('path') //这是一个文件路径的内置模块,在使用resolve属性时,就必需要有这个
const config = { // 多文件配置的写法
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
module.exports = config
复制代码
HtmlWebpackPlugin插件,该插件将为你生成一个 HTML5 文件, 其中包括使用 script
标签的 body 中的全部 webpack 包。 使用的时候,须要安装这个插件html-webpack-plugin
,基本具体配置以下:
var HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入插件
plugins: [
new HtmlWebpackPlugin({ // 能够配置更多的值,好比title
title: 'webpack', // 生成HTML标题
templat: './index.html' // 配置的模板文件
hash: true, // 防止缓存,给生成的JS文件添加一个hash值,包含css
cache: true // 仅在内容被更改时才更新文件
inject: true, // JS注入的位置,true,默认底部
chunks:['xxx', 'entry入口文件名'], // 多入口文件,会根据配置生成JS文件,默认所有引用
minify: {
removeAttributeQuotes: true, // 是否移除属性的引号 默认false
}
})
]
复制代码
参考资料: npm原文连接、webpack4 之html-webpack-plugin。更多配置查看前面的NPM原文连接
clean-webpack-plugin插件能对配置的输出文件进行清除,在build
命令开始执行的时候,会自动的去检查,若有有就会去先清除掉,这样就永远地保证了这个目录里面的文件都是最新的。可是也能够自定义删除某个目录,文档请戳这里。
const { cleanWebpackPlugin } = require('clean-webpack-plugin') // 先引入这个包,须要使用这种方式
plugins: [
new cleanWebpackPlugin() // 使用,默认不须要进行传参, 根据实测,在3.X的版本,能够直接引入
]
复制代码
若是要使用多个lodaer,只须要将规则rules
属性下面,放一个数组,里面能够有多个对象
module: {
rules: [{ // 关于多个loader配置以下,每一个loader都是一个对象
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},{
test: /\.m?js$/, // 匹配文件的后缀名
exclude: /(node_modules|bower_components)/, // 须要剔除的目录
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-object-rest-spread']
}
}
}]
}
复制代码
提示:其实,每个loader或者插件均可以使用单文件的方式,这样可让webpack文件变得不是那么的庞大,可是会形成项目根目录文件的变得不少,具体根据本身得项目来进行配置。
在转换TS文件的时候,须要下载typescript ts-loader
的依赖,安装好之后,须要在项目根目录新建一个为tsconfig.json
的JSON文件,配置以下:
{
"compilerOptions": {
"outDir": "./dist/", // 输出的文件,若是webpack已经配置了,就不须要了
"noImplicitAny": true,
"module": "es6", // 已什么模块的方式引入文件,主要有ES6和commonJS
"target": "es5", // 须要编译成什么类型的语法,ES5
"jsx": "react", // 使用jsx语法,
"allowJs": true // 在TS文件种是否存在ES的语法,默认true
},
"include": ['./src/', './'], // 引入的文件目录,须要编译的文件
"exclude": /node_modules/ // 剔除不须要编译的目录,多个使用(xx|xx)这种方式
}
复制代码
编译typescript
webpack的配置以下:
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}]
},
复制代码
lodash是一个一致性、模块化、高性能的 JavaScript 实用工具库,主要用于代码类型约束。在TS里面使用types/lodash
能够对TS进行类型约束。**提示:**lodash具备针对性,若是须要对jquery进行类型约束,则须要下载对应的lodash依赖包。
在图片打包之前,须要下载一个loader,使用file-loader
能够对图片进行打包,webpack经常使用配置以下:
module: {
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[path][name].[ext]' // 自定义名字,[name]名字[ext]扩展名,还能够加混淆好比哈希值
outputPath: 'images/' //将打包的文件,生成打包相对文件(dist)的images/的目录下
}
}]
}]
}
复制代码
说明:图片打包,默认文件名使用哈希进行混淆,也能够自定义配置。具体查看webpack官方的url-loader
说明,示例上仅仅只是显示了一部分。
若是图片须要进行BASE64的打包,须要下载url-loader
的一个loader,可是须要注意的是,file-loade
和url-loader
的处理是同样的,惟一不同的就是url-loader
要比file-loade
的功能更增强大,使用的方法和file-loade
的方法如出一辙,并无什么区别,可是url-loade
拥有更多的配置项。
module: {
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loade',
options: {
name: '[path][name].[ext]', // 自定义名字,[name]名字[ext]扩展名,还能够加混淆好比哈希值
outputPath: 'images/', //将打包的文件,生成打包相对文件(dist)的images/的目录下
limit: '1024', // 图片打包base的大小限制,单位是字节
}
}]
}]
}
复制代码
使用url-loader
会默认的将图片打包成了base64的文件,可是在正常状况下,只有在图片比较小的话,才会打成base的格式,所以须要在options
里面进行图片大小进行配置
在打包css的时候,须要下载一个css-loader
和style-loader
lodaer去作编译,若是要使用预编译,则须要安装对应的预编译loader。例如:sass须要 sass-loader
和node-sass
这两个loader,根据实验,只须要sass-loader
就能够了。
css-loader
用来打包css
style-loader
用来挂在页面,将这个loader自动挂在到html页面上,通常方式在html头部
基本配置信息以下(包含了sass预处理器):
module.exports = {
module: {
rules: [{
test: /\.css$/, // /\.sass$/
use: [ 'style-loader', 'css-loader' , 'sass-loader']
}]
}
}
复制代码
给CSS3添加前缀须要使用postcss-loader
lodaer和 autoprefixer
插件,继续使用上面的例子,基本配置以下:
module.exports = {
module: {
rules: [{
test: /\.css$/, // /\.sass$/
use: [ 'style-loader', 'css-loader' , 'sass-loader','postcss-loader'] // 写法1
use: [ 'style-loader', 'css-loader' ,'sass-loader',{ // 写法2,能够对每一个lodaer作单独配置
loader:'postcss-loader',
options: {
plugins: [
require("autoprefixer") /* 生成浏览器前缀 */
]}
}]
}]
}
}
复制代码
若是要让它自动添加前缀的话,须要在项目根目录新建一个文件(也能够是其它文件夹,全局配置通常都放在根目录),postcss.config.js
,在里面进行一个配置,也能够如上代码那样,写在webpack.config.js
中;基本配置信息以下:
module.exports = {
plugins: [ //插件的方式
require('autoprefixer') // 加载模块 默认写法
require('autoprefixer')({ // webpack或者postcss.config.js配置,
overrideBrowserList: 'last 100 versions'
})
]
}
复制代码
autoprefixer
在默认的配置下,只能自动的添加webkit
的前缀信息,若是须要添加更多的配置,还须要添加额外的参数。须要在
package.json
里面添加一个属性,属性名是browserslist
,对应的是一个数组,里面填入浏览器、或者node版本的名称,例如:"last 100 versions"
;若是须要在
webpack
里面配置的话,就须要使用overrideBrowserList
属性去替换browserslist
这个属性,不然在编译的时候,webpack
会有错误提示。关于browserslist
的具体用法,请点击这里
CSS模块化机制,简单配置:
module.exports = {
module: {
rules: [{
test: /\.css$/, // /\.sass$/ // 匹配的后缀名
use: [ 'style-loader', { // 配置须要的加载器,loader
loader: 'css-loader',
options: {
modules: {
localIdentNane: '[name]_[hash:base64:10]' // 自定义class的名字,bese须要截取
}
}
}, 'sass-loader']
}]
}
}
复制代码
若是开启了模块化操做,那么在模块化导入的时候,文件就只能是局部的方式导入,不然webpack在编译的时候,会出错,例子以下:
import 'xxx.css' // 全局引入
import xxx from 'xxx.ss' // 局部引入
复制代码
打包字体:打包字体和打包文件同样,loader用的也是打包图片的那个file-loader
module: {
rules: [{
test: /\.(woff|woff2|svg|ttf|eot )$/, // 字体文件后缀名
use: [{
loader: 'file-loader',
options: {
outputPath: 'font/' //生成的文件夹,将打包的文件,放在里面
name: [name].[ext] // 修改输出文件名,这样就默认原来的文件名和后缀名
}
}]
}]
}
复制代码
webpack-dev-server 可以用于快速开发应用程序,因此简称devServer;devServer是用来提升开发效率的,不是用devServer来作打包,它提供了一些配置项,能够用于改变devServer的默认行为,要配置devServer,除了能够在配置文件里经过devServer传入参数,还能够经过命令行传入参数。
/* 截取的vue cli的例子 经过命令传递的参数*/
"dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
复制代码
可是须要注意的是:devServer 只适用于开发模式
webpack开启本地服务器安装一个内置插件webpack-dev-server
来帮咱们开启一个本地服务,虽然webpackDevServer
插件是内置的,可是依然须要下载,只是不须要引入,须要在webpack.config.js
和package.json
文件进行配置,配置内容以下:
/* webpack.config.js 配置 */
module.exports = {
devServer: {
contentBase: './dist' // 配置打包的路径
open: true, // 设置是否默认打开
prot: "8080", // 端口
hot: true , // 开启热更新
}
}
/* package.json 配置 */
"scripts": {
"dev": "webpack-dev-server --open"
}
复制代码
在正确配置了webpack-dev-server
之后,从dist
目录中提供文件,默认地址是:localhost:8080
,也能够自定义配置,配置方案如上代码。更多配置点击这里,可是这种方法,会更新全部数据,能够理解成是"页面刷新"
webpack-dev-server在编译后不会写任何输出文件。相反,它将捆绑文件保存在内存中并为它们提供服务,就好像它们是安装在服务器根路径上的真实文件同样。若是您的页面但愿在不一样的路径中找到捆绑包文件,则可使用
publicPath
dev服务器配置中的选项更改此选项。
在使用webpack热更新的时候,须要有一个插件HotModuleReplacementPlugin
,简称是HMR,这个插件是webpack集成的一个内置插件,在使用的时候,不须要下载,可是要导入webpack
。
可是HotModuleReplacementPlugin
这个插件不能用于生产模式,热更新也是devServer里面的一个小项。 HotModuleReplacementPlugin
里面的配置项,能够忽略,官方说是实验性的,不推荐配置。
const webpack = require('webpack')
plugins:[
new webpack.HotModuleReplacementPlugin({
multiStep: true, // 将分两步构建 - 首先编译热更新块,而后编译剩余的普通资产
fullBuildTimeout: Number // 时间,表示启用 multiStep 这个之间的延迟
requestTimeout: Number // 时间(毫秒) 用于下载清单的超时时间
});
]
复制代码
请注意,
webpack.HotModuleReplacementPlugin
彻底启用HMR是必需的。若是webpack
或者webpack-dev-server
使用该--hot
选项启动,则会自动添加此插件,所以您可能不须要将此添加到您的webpack.config.js
。
"dev": "webpack-dev-server --host 0.0.0.0 " // 上述说明的实例,不须要webpack.config.js配置
复制代码
dev-server 使用了很是强大的 http-proxy-middleware 包。更多高级用法,请查阅其文档;在咱们作开发的时候,会由于跨域问题致使没法进行数据请求,所以须要借助wenpack
提供的反向代理,既porxy
,使用方法以下:
/* 省略其它代码 */
devServer: {
porxy: {
"/api": "http://localhost:3000", // 基础用法,将/api绑定到目标服务器。
"/api": { // 采用配置的用法,用法二
target: "http://localhost:3000", // 目标服务器
pathRewrite: {"^/api" : ""}, //路径重写,这样在前端访问的时候,就不须要传/api
secure: false, // 配置是否须要使用https的方式,默认是不容许
}
}, // 多个API请求同一服务器的写法
porxy:[{
context: ["/auth", "/api"], // 多个请求
target: "http://localhost:3000", // 目标服务器
}]
}
/*** 前端请求,已axions为例 ***/
axios.get('/api/xxx').then(red => { xxx })
axios.get('// xxx').then(red => { xxx }) // 配置请求的方式访问 忽略掉/api
复制代码
假设有多个API
请求都在同一个服务器,可使用context
上下文的方式跟上一个数组,代码如上例子,关于webpack proxy
的更多配置,请戳这里访问官网更多说明。
webpack 预加载
其实配置devtool就是在配置SourceMap,意为:源文件映射,可以快速找到代码出错的位置,可是这种方式,须要打包完了之后才能看到是否出错;可是也有折中的解决方案,须要在package.json
里面设置一个脚本,能够自动的为咱们打包,但它是一个文件协议,正常来讲,页面须要手动刷新,全部仍是使用devServer
的方式会比较好点儿。
module.export = {
/* cheap-module-eval-source-map 开发环境使用,速度快,包含三方模块 仅提示行错误*/
/* cheap-module-source-map 生产环境使用, 其实也能够不使用*/
/* source-map 源文件映射 inline-source-map(精准告诉你哪行那列出错)*/
devtool: 'cheap-module-eval-source-map'
}
/*================================= package.json ===============================*/
"scripts": {
"dev": "webpack --watch"
}
复制代码
devtool
您可使用SourceMapDevToolPlugin
, 而不是使用该选项,EvalSourceMapDevToolPlugin
由于它有更多选项。切勿同时使用devtool
选项和插件,该devtool
选项在内部添加插件,所以您最终会将插件应用两次。
devtool
您可使用SourceMapDevToolPlugin
, 而不是使用该选项,EvalSourceMapDevToolPlugin
由于它有更多选项其实这句话,我也不太理解是什么意思? 若是须要更加细粒度的控制,就使用SourceMapDevToolPlugin
这个插件,可是不能同时使用,缘由上面也说了。 其实就我我的以为,自带的就够了。
Tree Shaking翻译过来就是摇晃树枝 ,因此简称摇树;举个简单的例子:秋天的马路,马路两边种了不少行道树,有的叶子枯黄、有的叶子任然是绿色,由于枯黄的叶子随风飘落,为了更好的解决落叶的问题,就找了一个大型的器械,抓着树干使劲摇晃,枯黄的叶子也就掉落了下来,剩下的就是不易掉落的绿色叶子。
可是反过来能够这样理解,就是说,对于webpack
来讲,入口就至关因而一棵树干,入口上面有不少模块,这些模块就是树枝,可是这些依赖模块并无所有使用,或者只使用了模块中的某一个功能,这时就须要借助Tree Shaking
它将不须要的模块给摇掉,也就是说:在打包后文件里面,代码都是精简且须要的。原理请戳这里:Tree-Shaking性能优化实践 - 原理篇。
tree shaking 是一个术语,一般用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如
import
和export
。这个术语和概念其实是兴起于 ES2015 模块打包工具 rollup。新的 webpack 4 正式版本,扩展了这个检测能力,经过
package.json
的"sideEffects"
属性做为标记,向 compiler 提供提示,代表项目中的哪些文件是 "pure(纯的 ES2015 模块)",由此能够安全地删除文件中未使用的部分。
说了这么多? 那在webpack里面应该怎样使用呢?须要在package.json
中配置
{
"sideEffects": false, // 简单版本,
"sideEffects": [ // 使用数组的方式,我的理解:就是将不须要摇的东西给剔除出来,。
"@babel/polyfill",
"./src/some-side-effectful-file.js",
"*.css"
]
}
复制代码
这样只是标记“未使用代码(dead code)”,可是须要是在编译后(bulid)删除,所以:咱们须要切换到生产模式(production)来表示,命令模式(--optimize-minimize
);使用配置的方式,代码以下:
mode: 'production', // 必须是生产模式
optimization: { // 设置删除为标记的 未使用代码,也就是死代码
usedExports: true
}
复制代码
总结:Tree Shaking只在生产模式有效,开发模式无效,而且只能使用ESmodul的方式,详细说明请戳这里。
在vue cli 2.x的版本里面有不少webpack配置,一开始我也不知道为何须要这么配置,经过后来的了解才知道,在webpack
里面有不少模式,好比:生产模式、开发模式,介于它们之间的还有各类配置文件、公共模块等,如下是vue cli的webpack
配置目录。
.
|—— vue cli
|—— config
| |—— dev.env.js
| |—— index.js
| |—— prod.env.js
| |__ test.env.js
|—— build
| |—— build.js
| |—— check-versions.js
| |—— utils.js
| |—— vue-loader.conf.js
| |—— webpack.base.conf.js
| |—— webpack.dev.conf.js
| |__ webpack.prod.conf.js
|
.
复制代码
但是有这么多的单独的文件,最终须要怎么才能合并在一块儿呢? 是直接引入仍是须要使用插件呢?这里须要使用一个插件,这个插件的名字是:webpack-merge
,在使用它的时候,须要先去安装这个插件。
若是将上面的目录进行一个拆分,假设就只有webpack.prod.conf.js
、webpack.dev.conf.js
和webpack.base.conf.js
这么三个文件,其中webpack.base.conf.js
是一个公共文件,另外两个表示生产模式和开发模式。使用webpack-merge
的方法以下:
/* ======= 以生产模式为例 webpack.prod.conf.js======= */
const merge = require('webpack-merge');
const common = require('./webpack.prod.conf.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map'
});
复制代码
还须要在npm
里面进行配置,仍然已vue cli目录为例,在package.json
中加入如下内容:
scripts": {
"dev": "webpack-dev-server --open --progress --config build/webpack.dev.conf.js",
"build": "webpack --config build/webpack.prod.conf.js"
}
/* --progress 显示进度条的意思 */
复制代码
就只想使用一个文件入口,在里面配置一个环境变量,而后就能按照本身的须要,产生不一样的编译结果,实施方法能够按照以下操做。
那里面的commonConfig
文件和prodConfig
还有这个devConfig
它们也是一个单独的文件,为了不一个文件很大,因此在使用的过程当中,仍是单独分开写的比较好,只不过在具体配置的时候,使用一个环境变量来判断。
module.export = (env) => {
if(env && env.production){ // 条件判断
return merge(commonConfig, prodConfig); // 生产模式(线上模式)
} else {
return merge(commonConfig, devConfig); // 开发模式
}
}
复制代码
看了上面的代码,会想到一个问题,条件判断的变量env && env.production
是从哪里来的,其实这个变量是从package.json``里面配置得来的,也就是说:若是须要使用这种方式的话,就须要去更改启动脚本,代码以下:
scripts": {
"dev": "webpack-dev-server --open --progress --config build/webpack.base.conf.js",
"build": "webpack --env.production. --config build/webpack.base.conf.js"
}
/* --progress 显示进度条的意思 */
复制代码
能够很清除的看到dev
和build
这两个方法启动的目录文件都是同一个,可是在build
里面多了一个env.production
这个属性,其实,这个变量就是上面的条件判断语句所须要的参数。
环境变量还能够这样使用,就nodeJS
提供的process.env.NODE_ENV
,具体的时候方法,参考简书NodeJs/Vue项目中对process.env的使用。
在不少时候,咱们都须要使用eslint来帮助咱们进行代码检查和规范,这样每每在团队中,能作到风格统一,很是有利于团队协做、后期维护等,但配置eslint是很是繁琐的,下面一块儿来看看,咱们如何在webpack
中如何配置eslint
吧?在慕课手记中也有关于自定义的详细说明,更多查看请戳这里。
eslint
在使用前,须要先安装eslint
这个包,更多安装方法戳这里查看官方教程,安装方法以下:
npm install eslint --save-dev // 下载eslint这个安装包
npx eslint --init // 运行这个命令进行eslint的详细配置
复制代码
关于eslint
配置说明,当运行npx eslint --init
的时候,会弹出以下信息:
? How would you like to use ESLint? (Use arrow keys) //如何配置eslint这个文件,你想如何使用
To check syntax only // 仅检查语法
> To check syntax and find problems // 检查语法并找出问题
To check syntax, find problems, and enforce code style //检查语法、发现问题和强制代码样式
/* 我选择的是检查语法并找出问题,而后就有了下面的配置信息 */
? What type of modules does your project use? // 你想使用什么类型的模块呢?
JavaScript modules (import/export) // 基于ES6
> CommonJS (require/exports) // 这个主要是node中使用
None of these // 这些都不是
/* 由于在个人实验中,用的是vue,所需须要使用esmodule这种语法,而后弹出的信息以下 */
? Which framework does your project use? (Use arrow keys) // 你想使用那个框架
React
> Vue.js // 由于我是vue,全部选择vue
None of these // 其它框架
/* 当选择vue后,配置如以下,问你是在那个地方运行,能够多选,使用空格点亮,而后回车 */
? Where does your code run? (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Browser // 浏览器
( ) Node // node
/* 当我选择之后,会出现以下配置 固然是使用JS文件的方式,我的爱好*/
? What format do you want your config file to be in? (Use arrow keys) // 使用什么文件做为配置文件
> JavaScript
YAML
JSON
/* 问你是否想安装它们,这里固然选择是了 */
? Would you like to install them now with npm? (Y/n) // y 基础配置过程就完毕了
复制代码
当一些列配置完毕后,会发如今项目的根目录中有一个.eslintrc.js
的配置文件,这个文件就是eslint
的配置文件啦,更多配置请戳这里,也能够选择第三方公司的编码配置,安装方法参考eslint配置(使用Airbnb编码规则),比较变态的就是airbnb
规则了,很是的严格。
虽然这样在vscode
(前提是webpack
中有插件)中可使用了,可是对于其它的代码编辑器,却不会怎么报错? 因此还须要下载一个eslint-loader
才能够,具体操做方法以下:
npm install eslint-loader --save-dev
复制代码
下载好了之后,基本配置以下,可是必定要将eslint-loader
放在最后面,由于loader
的执行顺序是从后往前:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"]
}
]
}
};
复制代码
仅仅是这样(若是不作以下配置,只能在cmd
控制台显示),仍是不行滴,还须要一点儿额外的其它配置,配置以下:
module.exports = {
devServer: {
overlay: true // 在浏览器上弹出一个层,也就是在浏览器中显示全屏覆盖
}
};
复制代码
但都知道在vue cli
2 的版本中,当检测到警告信息,会输出在浏览器的控制台显示,可是这个问题,我还木有解决,有知道的小伙伴,能够告诉我如下。
overlay:{
warnings: false,
errors: true
}
复制代码
我也按照vue cli
源代码进行了一个配置,发现并无按照个人意愿来。
在刚学Vue.js
的时候,那时候运用的不是很熟,有时候须要操做DOM,可是Vue.js
操做DOM很是的不方便;有时候须要获取到 一个列表里面全部的标签,那时候就想到了使用Jquery
来帮我操做DOM,当时就觉得像其它JS
文件同样全局导入就能够了,可在实际运用的时候却傻眼了,就发现,可以在控制台输出Jquery
对象,可是却没法操做DOM
,这让我很抓狂。
通过学习,知道webpakc
自己就已经提供了一套关于shimming
解决方案:使用webpack
内置的ProvidePlugin
插件和expose-loader
,在网上找了不少文档,在使用webpack
内置的ProvidePlugin
插件的时候,虽然勉强解决这个问题,可是仍是有一些小问题,就eslint报错的问题。后来发现使用expose-loader
的方式也能够解决这个问题。
ProvidePlugin
是webpack
内置插件,所以在使用前须要导入webpack
,它主要的功能是:自动加载模块,而没必要处处 import
或 require
, 这也是webpack
推荐的使用方法。
可是:对于 ES2015 模块的 default export,你必须指定模块的 default 属性,由于它的名字;以
jquery
为例: 它实际上就是import $ from 'jquery'
,具体例子请看以下代码。更多例子请戳这里,查看官方示例说明。
const webpack = require('webpack')
/* 省略其它代码 */
plugins: [
new webpack.ProvidePlugin({
$: 'jquery', // 后面的jquery其实就是 import $ from 'jquery'
JQuery: 'jquery'
})
]
复制代码
能够经过expose-loader
向webpack
注入一个全局的公共模块,假设用了这个方法导入了jquery
的话,是能够全局使用,能够理解成绑定到了window
上,所以,在使用它的时候,须要下载这个expose-loader
,以jquery
为例,使用方法以下:
module: {
rules: [{
test: require.resolve('jquery'), // 导入jquery模块,但这个方法是node.js提供,和webpack无关
use: [{
loader: 'expose-loader',
options: 'jQuery' // 全局使用的名称, window.jQuery
},{
loader: 'expose-loader',
options: '$' // 全局使用名称, window.$
}]
}]
}
复制代码
可是这种方法,须要在局部导入(也就是说,须要每一个页面都须要导入),而后才能进行全局暴露,可是若是须要全局注入的话,显然这种方式不太稳当。
webpack
提供了一个Code Splitting
拆分代码,点击这里,能够查看代码分离的方式以及介绍。
使用CommonsChunkPlugin
用于避免它们的重复依赖,可是没法进一步优化;在webpack4
的版本之后,删除了optimization.splitChunks
这个属性;SplitChunksPlugin方法使用以下:
module.exports = {
splitChunks: {
chunks: "all", // 同步和异步都作分割
cacheGroups: { // 缓冲组
vendors:false,
default: false
}
}
}
复制代码
SplitChunksPlugin
的更多配置以下,具体参考官方说明,戳这里点击官方文档。
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // async 默认推荐使用,异步加载作分割, initial 同步 all 同步和异步都作分割
minSize: 30000, // 字节 引入的包或者模块大于这个这个值,才会作代码分割,只针对同步加载
maxSize: 0, // 超过 minSize 之后,会进行二次打包,默认不配置,不能强拆
minChunks: 1, // 引入次数,引入次数为多个,能够打包
maxAsyncRequests: 5, // 请求超过5个的部分不拆分,通常默认值
maxInitialRequests: 3, // 页面初始化同时发送的请求数量最大不能3个,超过之后就不会被拆分,默认值就会
automaticNameDelimiter: '~', // 用以代码分离打包的文件名默认链接符,cacheGroups里面的vendors
automaticNameMaxLength: 30, // 最长字节数,不能超过这个值
name: true, // 拆分的名字, true表示根据模块名和cacheGroups组的key来自动生成文件名
cacheGroups: { // 缓存配置,一个文件import多个库,若是须要多个库打包成一个文件,则使用这个缓存组
vendors: {
test: /[\\/]node_modules[\\/]/, // 检测引人的库是不是node_modules下面的
priority: -10, // 权重,决定哪一个组的优先配置
name: "vendors" // 自定义名字,不须要后缀名
},
default: { // 前面没有匹配上,则使用这个组,一般是业务代码
minChunks: 2, // 引入超过2次才重新打包成一个新文件
priority: -20, // 权重
reuseExistingChunk: true // 当为true后就不会重复打包,会进行自动复用。
}
}
}
}
};
复制代码
在chunks
的时候,还须要走cacheGroups
缓冲组,分离出来的文件名是:cacheGroups
里面的vendors
,中间的链接符是automaticNameDelimiter
,例vendors-main.js
cacheGroups
能够自定义多个组,若是多个组都知足要求,则以权重大的组为准。
webpack
为了更好的提高性能,最好的仍是使用异步的方式,这也是官方推荐。
当涉及到动态代码拆分时,webpack 提供了两个相似的技术。对于动态导入,第一种,也是优先选择的方式是:ES7提出的一个草案,既
import()
语法。第二种,则是使用 webpack 特定的require.ensure
。先来讲说第一种:
动态导入最大的好处就是实现了懒加载,用到那个模块就才会调用那个模块,很是适合ASP导入,能够大大的减小等待时间,但使用这个须要用到babel
去转,也就是须要下载@babel/plugin-syntax-dynamic-import
这个插件,使用方法以下:
{
"plugins": ["@babel/plugin-syntax-dynamic-import"] // 使用插件,在webpack plugins里面添加
}
复制代码
在页面添加动态import
导入懒加载的方式,这种方式会返回一个promise
对象,代码以下:
/* 声明这个方法 须要一个函数 */
function getComponent() {
import(/* webpackChunkName: "lodash" */ 'lodash').then( _ => {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}).catch( error => 'An error occurred while loading the component');
}
/* 调用这个方法 若是不调用则不执行*/
getComponent().then(component => {
document.body.appendChild(component);
})
复制代码
这种方法,则会对代码进行分离,将动态打入的第三方模块,会单独的打一个文件,而本身所写的业务代码,则会被打包到新的一个文件里面。
/* webpackChunkName: "lodash" */ 'lodash'
魔法字符串,懒加载,配合SplitChunksPlugin
使用,能够进行一个单独配置,其实这个配置和SplitChunksPlugin
是共用。
module.exports = {
splitChunks: {
cacheGroups: { // 缓冲组
vendors: {
test: /[\\/]node_modules[\\/]/, // 检测引人的库是不是node_modules下面的
priority: -10, // 权重,决定哪一个组的优先配置
name: "vendors" // 自定义名字,不须要后缀名
},
default: false
}
}
}
复制代码
可是使用懒加载的时候,会有一个弊端,就是当用户须要一个模块的时候,只有触发之后,才能去加载对应的模块,这就会形成一个网络的延时,有一个等待期,不利于用户体验,因此就须要另一个技术,也就是预加载,当页面自动加载出来之后,利用闲置的带宽,去加载其它可能须要的模块。
这个是webpack 4.6.0
新增的一个功能,用通俗的话来讲,就是在网络空闲的时候,才去自动下载其它的非必要模块,这种方式极大的提高了用户体验,也解决了等待延时问题,但是要怎么使用呢?
/* 使用方法和上面的 懒加载是同样的,只是这个魔法字符串里面的参数不同 */
import(/* webpackPrefetch: true */ 'LoginModal');
复制代码
在声明导入时使用这些内联指令容许webpack输出“Resource Hint”,使用方法如上,它告诉浏览器:
区别:preload
是和核心代码一块儿加载,prefetch
预加载
缓存带来的代码提高是有限的,因此使用懒加载代码优化是如今性能优化的主要使用方式。
关于打包分析,官网为咱们提供了一个内置插件Bundle Analysis
,在guider
下面的Code Splitting
里面的Bundle Analysis
,他有几种类型,webpack-chart:(交互式饼图)等类型,具体请戳这里。使用方法也很简单,只须要在package.json
中配置一段脚本便可,例子以下:
scripts": {
"build": "webpack --profile --json > stats.json --env.production. --config build/webpack.base.conf.js"
}
复制代码
其实也能够直接使用webpack --profile --json > stats.json
, 打包完成后,会生成一个stats.json
文件,这个文件就是结果分析,而后须要将这个文件上传到这个网站,进行数据分析,可是这个表,感受很是不人性化,我的以为很难看,因此须要使用webpack-bundle-analyzer
插件。
Webpack Bundle Analyzer
也是一个打包分析,它和webpack-chart
不一样的是,Webpack Bundle Analyzer
是一个很直观的平面图,这是一个插件,因此在使用的时候,须要进行下载安装,npm install --save-dev webpack-bundle-analyzer
。
若是使用了这个之后,在打包的时候,控制台不会输出打包信息,好比文件、 打包时间、chunk等信息,这也算是一个弊端。
在使用它的时候,也须要有如上的package.json
中配置(实测,可不须要使用),在webpack
里面的基本配置以下,更多配置请戳这里;
// 导入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 使用它
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
复制代码
它默认是打包在js
文件里面,这样就会致使文件变得很大,因此将CSS
代码分离出来是颇有必要的,使用CSS
代码分析则须要使用一个插件来帮咱们完成,这个通常只作线上版本,在开发版本则不须要,这个插件的名字是MiniCssExtractPlugin
,点击这里查看更多官方配置。
npm install --save-dev mini-css-extract-plugin // 须要先安装
复制代码
简单的配置说明,在使用的时候须要将style-loader
替换为MiniCssExtractPlugin.loader
,若是须要在开发环境中使用热更新,须要从新进行一个设置。
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 导入插件
module.exports = {
plugins: [
new MiniCssExtractPlugin({ // 方法同output里面的方法,最好就是加一个hash值,防止缓存
filename: '[name].css', // 生成的文件名
chunkFilename: '[id].css', // 构建时生成,主要是防止文件更新浏览器不更新,缓存致使的
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader, // 替换style-loader
options: { // 其它的配置项
publicPath: '../', // 指定的地址,同等与output里面的publicPath
hmr: process.env.NODE_ENV === 'development', // 环境变量
},
},
'css-loader',
],
},
],
},
};
复制代码
CSS代码分离,默认是不会被压缩,若是想要代码压缩,则须要使用optimize-css-assets-webpack-plugin
插件,但须要注意的是:设置optimization.minimizer
会覆盖webpack提供的默认值,所以须要指定,JS minimalizer, 使用方法以下:
const TerserJSPlugin = require('terser-webpack-plugin'); // 不添加则JS不会被压缩
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 导入CSS分离插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // CSS代码压缩
module.exports = {
optimization: { // 添加一个属性,
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
复制代码
设置optimization.minimizer
会致使重写,因此须要下载terser-webpack-plugin
这个插件,去压缩JS
,若是没有这个插件就会致使,CSS
代码被压缩了,可是JS
代码则不会被压缩。
Webpack 在启动后会从配置的入口模块出发找出全部依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也能够根据本身的须要修改默认的规则。
关于resolve
的基本配置以下,更多配置戳这里查看官方教程。
const path = require('path') // node提供的文件路径模块
module.exports = {
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json'], // 自动解析肯定的扩展,能够省略的扩展名
alias: { //建立 import 或 require 的别名,来确保模块引入变得更简单。
"@": path.resolve(__dirname, '../src'),
xyz$: path.resolve(__dirname, 'XXX.js') // 精确匹配 xyz
}
}
};
复制代码
在搭建vue
脚手架以前,须要下载两个loader
,分别是:vue-loader
和 vue-template-compiler
,具体的安装方法查看Vue loader官方说明。
npm install -D vue-loader vue-template-compiler // 安装
复制代码
须要注意的问题:每一个
vue
包的新版本发布时,一个相应版本的vue-template-compiler
也会随之发布。编译器的版本必须和基本的vue
包保持同步,这样vue-loader
就会生成兼容运行时的代码。这意味着你每次升级项目中的 vue 包时,也应该匹配升级 vue-template-compiler。
在main.js
中加入最基本的代码,这也是最基本的入口配置信息
import Vue from 'vue'
window.vm = new Vue({
el: '#app',
render: h => h(App),
components: { App },
template: '<App/>'
})
复制代码
正常来讲,webpack
在编译的时候,是不认识.vue
的后缀名,所以须要咱们手动的在webpack
里面新增一个loader
,用于识别.vue
文件,基本配置以下:
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.js$/,
loader: 'babel-loader'
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
// 请确保引入这个插件来施展魔法
new VueLoaderPlugin()
]
}
复制代码
vue loader15+
的版本都须要去使用一个vue-loader/lib/plugin
插件,这个插件不用单独下载,在使用vue-loader
的时候,这个插件就已经有了。
为了更快的使用打包优化,须要及时更新nodeJS
以及webpack
的版本,尽量的减小一些loader
的转换,而且须要剔除一些没必要要的目录,好比:node-modules
的文件夹,插件尽量得时候使用官方推荐。 还可使用webpack
内置的DllPlugin
插件去帮咱们进行打包速度优化。
DllPlugin
插件专门用于单独的webpack配置中,以建立仅限dll
的捆绑包。它建立了一个manifest.json
文件,用于DllReferencePlugin
映射依赖项(先建立一个,而后进行复制,这样就提高了打包效率)。
更多的方法戳这里查看官方教程,一般是建立一个webpack.dll.js
文件,用来专门配置dllplugin
,目的是为了将一些公共的库,打包成一个文件,后续打包不会从新打包,只会去引用打包好的包。
const path = require('path') // 导入node 模块
module.exports = {
mode: 'production', // 生产模式
entry: {
vendors: ['vue','jquery'] // 入口文件,自定义属性
},
output:{
filename: '[name].dll.js', // 输出,多个文件打包成一个文件
path: path.resolve(__dirname, '../dll'), // 打包的文件夹目录
library: '[name]' // 导出全局变量,用于其它文件
}
}
复制代码
可是这种方法,不会自动添加到html
页面中,所以须要一个插件add-asset-html-webpack-plugin
,来帮我咱们将webpack.dll.js
打包后文件自动添加到html
中,关于更多说明戳这里查看NPM
教程
cnpm i add-asset-html-webpack-plugin -D // 安装
复制代码
使用它的时候,此时就不能写在webpack.dll.js
文件中,应该写在公共的文件当中,好比webpack.base.conf.js
文件里面,基本配置方法以下:
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 导入自动生成html插件
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); // 自动添加插件
const webpackConfig = {
entry: 'index.js',
output: {
path: 'dist',
filename: 'index_bundle.js',
},
plugins: [
new HtmlWebpackPlugin(),// 更多说明请看前面的 HtmlWebpackPlugin 配置
// require.resolve和 path.resolve, 在这里的用法是同样的,
new AddAssetHtmlPlugin({ filepath: require.resolve('../dll/vendors.dll.js') }),
],
};
复制代码