前言:webpack是我们现代前端最常见的模块打包工具,webpack 是一个模块打包器,主要目的是在浏览器上打包 JavaScript 文件。特性:打包 CommonJs 和 AMD 模块(以及绑定),可创建单个或多个按需加载的块,以减少初始加载时间,在编译期间会解决依赖关系,减少了运行时的大小,加载器可以在编译时预处理文件,如 coffee-script 到 javascript。
小编也是从闲余时间零开始搭建webpack,作为演示讲解一些常用的功能块和属性方法
体积减少80%!释放webpack tree-shaking的真正潜力
package.json文件描述
相关属性依赖版本号,并非是最新版本,实际开发请重新安装依赖
{
//从name到private都是package的配置信息,也就是我们在脚手架搭建中输入的项目描述
"name": "shop",//项目名称:不能以.(点)或者_(下划线)开头,不能包含大写字母,具有明确的的含义与现有项目名字不重复
"version": "1.0.0",//项目版本号:遵循“大版本.次要版本.小版本”
"description": "webpack project",//项目描述
"author": "njt",//作者名字
"private": true,//是否私有
"scripts": { //scripts中的子项即是我们在控制台运行的脚本的缩写
//①webpack-dev-server:启动了http服务器,实现实时编译;
//inline模式会在webpack.config.js入口配置中新增webpack-dev-server/client?http://localhost:8080/的入口,使得我们访问路径为localhost:8080/index.html(相应的还有另外一种模式Iframe);
//progress:显示打包的进度
"start": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",//与npm run dev相同,直接运行开发环境
"build": "node build/build.js"//使用node运行build文件
"analyz": "NODE_ENV=production npm_config_report=true npm run build", // 查看打包体积大小分布
},
"dependencies": { //②dependencies(项目依赖库):在安装时使用--save则写入到dependencies
"vue": "^2.5.2",//vue.js
"vue-router": "^3.0.1"//vue的路由插件
},
//和devDependencies(开发依赖库):在安装时使用--save-dev将写入到devDependencies
"devDependencies": {
"autoprefixer": "^7.1.2",//autoprefixer作为postcss插件用来解析CSS补充前缀,例如 display: flex会补充为display:-webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex。
//babel:以下几个babel开头的都是针对es6解析的插件。用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本
"babel-core": "^6.22.1",//babel的核心,把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。
"babel-helper-vue-jsx-merge-props": "^2.0.3",//预制babel-template函数,提供给vue,jsx等使用
"babel-loader": "^7.1.1",//使项目运行使用Babel和webpack来传输js文件,使用babel-core提供的api进行转译
"babel-plugin-syntax-jsx": "^6.18.0",//支持jsx
"babel-plugin-transform-runtime": "^6.22.0",//避免编译输出中的重复,直接编译到build环境中
"babel-plugin-transform-vue-jsx": "^3.5.0",//babel转译过程中使用到的插件,避免重复
"babel-preset-env": "^1.3.2",//转为es5,transform阶段使用到的插件之一
"babel-preset-stage-2": "^6.22.0",//ECMAScript第二阶段的规范
"chalk": "^2.0.1",//用来在命令行输出不同颜色文字
"copy-webpack-plugin": "^4.0.1",//拷贝资源和文件
"css-loader": "^0.28.0",//webpack先用css-loader加载器去解析后缀为css的文件,再使用style-loader生成一个内容为最终解析完的css代码的style标签,放到head标签里
"mini-css-extract-plugin": "^0.9.0",//将一个以上的包里面的css文本提取到单独文件中
"file-loader": "^1.1.4",//③打包压缩文件,与url-loader用法类似
"friendly-errors-webpack-plugin": "^1.6.1",//识别某些类别的WebPACK错误和清理,聚合和优先排序,以提供更好的开发经验
"html-webpack-plugin": "^2.30.1",//简化了HTML文件的创建,引入了外部资源,创建html的入口文件,可通过此项进行多页面的配置
"node-notifier": "^5.1.2",//支持使用node发送跨平台的本地通知
"ora": "^1.2.0",//加载(loading)的插件
"portfinder": "^1.0.13",//查看进程端口
"postcss-import": "^11.0.0",//可以消耗本地文件、节点模块或web_modules
"postcss-loader": "^2.0.8",//用来兼容css的插件
"postcss-url": "^7.2.1",//URL上重新定位、内联或复制
"webpack-parallel-uglify-plugin": "^1.1.2",//压缩js文件
"url-loader": "^0.5.8",//压缩文件,可将图片转化为base64
"vue-loader": "^13.3.0",//VUE单文件组件的WebPACK加载器
"vue-style-loader": "^3.0.1",//类似于样式加载程序,您可以在CSS加载器之后将其链接,以将CSS动态地注入到文档中作为样式标签
"vue-template-compiler": "^2.5.2",//这个包可以用来预编译VUE模板到渲染函数,以避免运行时编译开销和CSP限制
"webpack": "^4.43.0",//打包工具
"webpack-cli": "^3.3.12",
"webpack-bundle-analyzer": "^2.9.0",//可视化webpack输出文件的大小
"webpack-dev-server": "^3.11.0",//提供一个提供实时重载的开发服务器
"webpack-merge": "^4.2.2"//它将数组和合并对象创建一个新对象。如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中
},
"engines": { //engines是引擎,指定node和npm版本
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [ //限制了浏览器或者客户端需要什么版本才可运行
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
1. 代码示例 webpack.base.js公共编译模块:入口、出口、编译模块配置、代码块分割、性能警告、插件等
const os = require('os')
const path = require('path');
const webpack = require('webpack');
const HappyPack = require('happypack')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // webpack4官网未同步,改变了引用方式
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const happyThreadPool = HappyPack.ThreadPool({
size: os.cpus().length
})
module.exports = {
entry: { // 项目编译的文件入口地址,也是配置多文件入口地址
main: './src/index.js',
},
output: { // 项目编译文件的出口地址
// 编译生成的js文件存放在根目录下的js目录下,如果js文件夹不存在就自动创建
filename: 'js/[name].[chunkhash].js', // 「入口分块(entry chunk)」的文件名模板(出口分块)
path: path.resolve(__dirname, '../dist'), // 所有输出文件的目标路径
publicPath: "/assets/", // 输出解析文件的目录,url 相对于 HTML 页面
// 常用与封装第三方库使用,项目内注释掉
// library: "MyLibrary", // 导出库(exported library)的名称
// libraryTarget: "umd", // 通用模块定义
},
resolve: { // 解析模块请求的选项
extensions: [".js", ".json", ".jsx", ".css"], // 使用的扩展名
alias: { // 模块别名列表
'@': path.resolve(__dirname, '../src')
}
},
module: {
rules: [
{
test: /\.js$/, // 这里是匹配条件,每个选项都接收一个正则表达式或字符串
loader: 'happypack/loader?id=happyBable', // 应该应用的 loader,它相对上下文解析
exclude: /node_modules\/(?!module-[^\s]+)/, // exclude 是必不匹配选项(优先于 test 和 include)
include: [
path.resolve(__dirname, '../src'),
path.resolve(__dirname, 'node_modules/webpack-dev-server/client'),
/module-[^\s]+/i,
]
},
{
test: /\.css$/,
use: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader']
}
]
},
optimization: { // 代码分割文件依赖公共模块部分
splitChunks: {
chunks: "all", //initial、async和all
minSize: 30000, //形成一个新代码块最小的体积
minChunks: 2, //在分割之前,这个代码块最小应该被引用的次数
maxAsyncRequests: 5, //按需加载时候最大的并行请求数
maxInitialRequests: 3, //最大初始化请求数
automaticNameDelimiter: '~', //打包分割符
name: 'commons', //打包后的名字
// 会抽离出第三库的代码,生成一个vendors.db5b3f3dc26a5ebe685c.js文件,且每次打包第三库的hash也不会变化,
// 这样就会利用浏览器的缓存机制,达到一个缓存优化,除非第三方库的版本变化,会重新生成一个新的文件,这样浏览
// 器也会重新请求新的文件。
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, //用于控制哪些模块被这个缓存组匹配到、它默认会选择所有的模块
priority: -10, //缓存组打包的先后优先级
name: 'vendors',
minSize: 3000
},
styles: {
name: 'styles',
test: /\.(c|le)ss$/,
chunks: 'all',
enforce: true,
},
default: {
minChunks: 2,
priority: -20,
minSize: 3000
}
}
},
},
performance: { // 性能提示
maxEntrypointSize: 2.5 * 1024 * 1024, // 整数类型(以字节为单位)
maxAssetSize: 3 * 1024 * 1024 // 整数类型(以字节为单位)
},
plugins: [
new CleanWebpackPlugin(), // 在每次构建前清理 /dist 文件夹。
new HtmlWebpackPlugin({ // 为您生成HTML文件,其余参数详看https://github.com/jantimon/html-webpack-plugin
inject: 'body',
minify: { // 压缩html
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
template: path.resolve(__dirname, '../index.html')
}),
new HappyPack({ // 将文件解析任务分解成多个子进程并发执行
id: 'happyBable', // id 标识符,要和 rules 中指定的 id 对应起来
loaders: ['babel-loader?cacheDirectory'], // babel-loader开启缓存;需要使用的 loader,用法和 rules 中 Loader 配置一样
threadPool: happyThreadPool, // 对象,代表共享进程池, 以防止资源占用过多
verbose: true // 是否允许 happypack 输出日志,默认是 true
}),
new MiniCssExtractPlugin({ // 为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件,并支持 CSS 和 SourceMap 的按需加载
filename: "css/[name].[chunkhash].css",
ignoreOrder: false,
})
],
};
补充:MiniCssExtractPlugin插件,提取js文件中的css文件,以link链接在index.html文件中引入,注意:当style-loader 和 css-loader方式在生产环境构建不生效时,原因:1.是否开启 tree shaking, 将package.json文件中的"sideEffects"去掉或者改为自己匹配的文件;2. 查看rules规则配置的loader是否正确,MiniCssExtractPlugin插件不支持style-loader。3.配置完MiniCssExtractPlugin插件会生成多个bundle.css包,可以通过配置cacheGroups单独打包成一个文件。
2. 代码示例 webpack.dev.js开发环境编译模块:mode、devServer、plugins等
const ip = require('ip') // 获取本地ip地址
const path = require('path')
const merge = require('webpack-merge'); // 在环境特定配置 merge() 合并 dev 和 prod常见配置
const common = require('./webpack.base.js'); // 编译环境公共模板
const packageConfig = require('../package.json')
const CopyWebpackPlugin = require('copy-webpack-plugin') // 拷贝静态文件资源
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const devConfig = {
mode: 'development', // 环境区分:"production" | "development" | "none"
devtool: 'inline-source-map', // 通过在浏览器调试工具(browser devtools)中添加元信息(meta info)增强调试,详细配置请点击https://www.webpackjs.com/configuration/devtool/
devServer: {
contentBase: '../dist', // 服务的访问地址,也是你本地编译完后的访问地址
port: 8081, // 端口的位置
quiet: true, // 关闭编译终端出错,这与FriendlyErrorsWebpackPlugin相关联,控制台编译出错
overlay: true, // 编译出现错误时,将错误直接显示在页面上
historyApiFallback: { // 404响应页面 默认index.html
rewrites: [{
from: /.*/,
to: path.posix.join('/', '../index.html')
}, ],
}
}
};
// 启动消息提示在终端显示
const createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return false;
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png'),
sound: true
})
}
}
module.exports = merge(common, devConfig, {
plugins: [
new FriendlyErrorsWebpackPlugin({ // 去除终端webpack多余编译信息使界面可观
compilationSuccessInfo: {
messages: [`Your application is running here: http://${ip.address()}:${devConfig.devServer.port}`],
},
onErrors: () => createNotifierCallback(),
clearConsole: true,
}),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../static'),
to: "static"
}
]
}),
]
})
- FriendlyErrorsWebpackPlugin 去除终端webpack多余编译信息使界面可观
3. 代码示例 webpack.prod.js生产环境编译模块:mode、devtool、plugins等
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = merge(common, {
mode: 'production', // 生产环境,默认开启更小的代码量以加快网页的加载速度,对源代码进行压缩
devtool: false,
plugins: [
new webpack.DefinePlugin({ //自定义一个plugin 生成当前环境下的一个变量
'process.env.NODE_ENV': JSON.stringify('production')
}),
new ParallelUglifyPlugin({ // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
uglifyJS: { // 传递给 UglifyJS 的参数
output: {
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
},
warnings: false, // 在UglifyJs删除没有用到的代码时不输出警告
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
},
}),
new BundleAnalyzerPlugin()
]
});
4.补充知识点 Chunk理解
Chunk是指webpack里的一个代码块。对于module,Webpack可以看做是模块打包机,我们编写的任何文件,对于Webpack来说,都是一个个模块。而Chunk是webpack打包过程中,一堆Module的集合。Chunk和Bundle,Bundle就是我们最终输出的一个或多个打包文件。产生Chunk的三种途径: entry入口、异步加载模块、代码分割。
5.webpack热更新原理:
- Webpack编译期,为需要热更新的 entry 注入热更新代码(EventSource通信)
- 页面首次打开后,服务端与客户端通过 EventSource 建立通信渠道,把下一次的 hash 返回前端
- 客户端获取到hash,这个hash将作为下一次请求服务端 hot-update.js 和 hot-update.json的hash
- 修改页面代码后,Webpack 监听到文件修改后,开始编译,编译完成后,发送 build 消息给客户端
- 客户端获取到hash,成功后客户端构造hot-update.js script链接,然后插入主文档
- hot-update.js 插入成功后,执行hotAPI 的 createRecord 和 reload方法,获取到 Vue 组件的 render方法,重新 render 组件, 继而实现 UI 无刷新更新。
5.webpack编译原理
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
- 确定入口:根据配置中的 entry 找出所有的入口文件;
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
本篇文章只是对webpack常用到的属性做了相关注解,实际的开发中相关文章也有说明,常去敲一敲也会遇到一些不常见的问题,对成长也是一种帮助吧!常看看官网其余属性和插件也有说明。
最后推荐一篇文章webpack是怎么实现动态导入的