后面咱们说了 webpack 的一些根底,当初咱们来应用 webpack 理论来编译写个我的项目。
用 vue-cli 创立一个我的项目,而后把它的 vue-cli-service 以及 webpack 等黑盒工具移除,而后咱们来本人编译它。
首先咱们要创立三个文件
- webpack.common.js 公共的 webpack 配置
- webpack.dev.js 开发阶段的配置
- webpack.prod.js 生产阶段的配置
首先咱们来编写 webpack.common 文件
const HtmlWebpackPlugin = require('html-webpack-plugin')// html 模板
const webpack = require('webpack')
const PreloadWebpackPlugin = require('preload-webpack-plugin')// 预加载
const path = require('path')
const os = require('os')
const VueLoaderPlugin = require('vue-loader/lib/plugin')// vue 对应插件
const workers = { // 多线程编译
loader: 'thread-loader',
options: {workers: os.cpus().length
}
}
module.exports = {// entry: ['babel-polyfill', './src/main.js'],
entry: './src/main.js', // 入口
output: {filename: 'bundle.js' // 输入},
optimization: {
concatenateModules: true, // 尽可能合并模块到一个函数
splitChunks: { // 公共代码拆分
// include all types of chunks
chunks: 'all'
},
runtimeChunk: true // 运行时代码 拆出来 缓存
},
resolve: {
// modules: [// 解析时搜寻得模块, 配了导致 ie 报错
// path.resolve(__dirname, 'node_modules'),
// ],
alias: { // 配置别名
components: path.resolve(__dirname, 'src/components'),
src: path.resolve(__dirname, 'src')
},
extensions: ['.js', '.json', '.vue'] // 省略后缀
},
module: {
rules: [
{ // babel
test: /\.js$/,
exclude: /node_modules/,
use: [
workers,
{
loader: 'babel-loader',
options: {
// babel-polyfill 按需引入, 差了 1m 左右 core polyfill 原型
presets: [['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3}]],
cacheDirectory: true, // 开启缓存 第二次构建时,会读取之前的缓存, 吃内存,不开发了记得清下占用
// presets: ['@babel/preset-env']
plugins: [// import()语法反对
'@babel/plugin-syntax-dynamic-import'
]
}
}
]
},
{test: /\.(gif|png|jpe?g|svg)$/i, // 不超过 10kb 转为 data:urlbase64
use: [
{
loader: 'url-loader',
options: {
limit: 10 * 1024, // 10 KB
name: 'image/[name]-[contenthash:8].[ext]', // 输入 hash
esModule: false
}
}]
},
{
test: /\.vue$/, // 解析 vue 文件
use: [
workers,
'vue-loader'
]
}
]
},
plugins: [
new PreloadWebpackPlugin({ // 退出预解析和预加载 webpack4 之后下载 preload-webpack-plugin@next 最新版
rel: 'preload',
as (entry) {console.log(entry)
if (/\.css$/.test(entry)) return 'style'
if (/\.woff$/.test(entry)) return 'font'
if (/\.(png|jpe?g|gif)$/.test(entry)) return 'image'
return 'script'
},
include: 'allChunks',
fileBlacklist: [/\.(map)$/, /runtime~.+\.js$/]
// 把 runtime 文件抽离进去,因为 import()运行时文件变动了,runtime 治理运行时 babel 导入文件的 hash 也会变动,// 默认 webpack 打包进去的依赖入口文件会把 runtime 导入,这样会导致它的 hash 也会变动,这样就会导致缓存生效,// 所以把 runtime 这个文件加载到 html 中,从以来入口文件中抽离,来防止依赖入口的 hash 变动。这样它就不须要进行预加载了。}),
new HtmlWebpackPlugin({ // html 模板
title: 'Vue dev App',
template: path.resolve(__dirname, 'public/index.html')
}),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
// 值要求的是一个代码片段
BASE_URL: '"/"'
})
]
}
代码里都有正文,而后咱们做下思路剖析:
- 确定打包造成依赖图的入口文件和输入文件名字
- 配置别名 resolve 不便我的项目开始时快捷引入
- 配置 loader,首先解析 vue 文件须要 vue-loader 和 vue-loader/lib/plugin 插件, 解析图片应用 url-loader 配置大小(10kb 以下转换为 base64 超出的 copy 图片)和文件门路以及文件名 contenthash:8 内容级别的 hash,vue 的图片导入默认应用的 CommonJs 标准,所以标注 esModule 不转为 esm, 最初配置 babel-loader 转换 js 个性,首先下载 babel-core 外围模块和 env 编译所有新个性,指定不须要编译的文件夹,babel-polyfill 配置上兼容新的 api(Iterator、Generator、Set、Maps、Proxy、Reflect),而后配置 useBuiltIns 按浏览器短少的 polyfill 按需加载,而后配置 core:3 兼容 原型的办法(array.includes)(https://www.cnblogs.com/dh-dh/p/10071312.html
)
- 而后咱们配置了 workers,在第一次编译后开启多线程打包,只对可能影响编译速度的 loader 增加。
- 而后配置 optimization 属性,尽可能合并模块到一个函数,拆分公共代码,而后把运行时得代码拆分进去(import()的代码)
- 而后配置插件,首先是预加载插件,html 模板,vueLoader 的插件,设置门路变量。
而后再配置开发阶段配置 webpack.dev
const {merge} = require('webpack-merge')// 合并配置插件
const common = require('./webpack.common')// 公共配置
const path = require('path')
const os = require('os')
const webpack = require('webpack')
const workers = { // 多线程编译
loader: 'thread-loader',
options: {workers: os.cpus().length
}
}
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
// devtool:'source-map',
devServer: {
hot: true,
contentBase: path.join(__dirname, 'public'),
open: true,
stats: 'errors-only',
clientLogLevel: 'none',
disableHostCheck: true
},
module: {
rules: [
{test: /\.(js|vue)$/, // eslint
enforce: 'pre',
exclude: /node_modules/,
include: path.resolve(__dirname, 'src'),
loader: [workers, 'eslint-loader']
},
{
test: /\.less$/, // 解析引入得 less
use: [
workers,
'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.css$/, // 解析组件外部 style
use: [
workers,
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
},
plugins: [new webpack.HotModuleReplacementPlugin() // 热加载
]
})
- 这个文件很简略,咱们就须要合并下公共配置,而后加一些开发阶段的非凡配置
- 配置模式开发,配置 source-map,配置 devServer 服务热加载,读取资源目录
- 配置 loader,首先下载 eslint 相干依赖,生产 eslint 配置,配置 eslint-loader,less 和 css 的 loader,咱们这里应用了 postcss 以及它的相干插件
postcss.config.js
const postcssConfig = {
plugins: [ // 配合 package 的 browserslist
require('postcss-import'),
require('postcss-cssnext'),
require('cssnano')
]
}
console.log(process.env.NODE_ENV)
if (process.env.NODE_ENV === 'production') { // 应用 postcss 插件形式实现,webpack 插件形式太麻烦了
const purgecss = require('@fullhuman/postcss-purgecss')
const purgecssConfig = require('./purgecss.config')
postcssConfig.plugins.push(purgecss(purgecssConfig))
}
module.exports = postcssConfig
这个外面引入了一个生产阶段对 vue 外部 style css 摇树的插件配置,配置我贴出来
purgecss.config
module.exports = {content: ['./public/**/*.html', './src/**/*.vue'], /// / 清理范畴,所有的 vue component 和和入口的 html,js 里引入的默认是全局应用的
defaultExtractor (content) {const contentWithoutStyleBlocks = content.replace(/<style[^]+?<\/style>/gi, '')
return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []},
whitelist: [/el-.+$/], // 将 elementuijs 应用上的类也放在白名单不作解决
whitelistPatterns: [/-(leave|enter|appear)(|-(to|from|active))$/, /^(?!(|.*?:)cursor-move).+-move$/, /^router-link(|-exact)-active$/, /data-v-.*/, /el-.+$/]
}
- 最初配置一个热加载的插件
最初咱们配置生产文件
webpack.prod
const {merge} = require('webpack-merge')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')// 打包之前清空
const CopyWebpackPlugin = require('copy-webpack-plugin')// copy 文件插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')// 把 css 抽成 link 的形式
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')// 压缩 css
const TerserWebpackPlugin = require('terser-webpack-plugin')// 压缩 js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')// 匹配 runtime.chunk 文件写到 html 中
const common = require('./webpack.common')
const path = require('path')
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer') // webpack 打包后果剖析
console.log(common)
// common.module.rules.push({// test: /\.(gif|png|jpe?g|svg)$/i,
// // 这个依赖下载要用 cnpm 淘宝源,并且用管理员权限下载, 下不下来把依赖都删了,而后从新下载,其余都下不来,亲身经历
// // 算了不必它了,通过它压缩过后的图片包含 base64,在 ie 中不能解析
// loader: 'image-webpack-loader',
// options: {
// mozjpeg: {
// progressive: true,
// },
// // optipng.enabled: false will disable optipng
// optipng: {
// enabled: false,
// },
// pngquant: {// quality: [0.65, 0.90],
// speed: 4
// },
// gifsicle: {
// interlaced: false,
// },
// // the webp option will enable WEBP
// webp: {
// quality: 75
// }
// }
// })
module.exports = merge(common, {
mode: 'production',
output: {path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].bundle.js', // 输入 name hash
chunkFilename: '[name].[contenthash:8].chunk.js' // chunk 文件 hash
},
devtool: 'none',
module: {
rules: [
{test: /\.(less)$/,
use: [
MiniCssExtractPlugin.loader, // 生成环境,最初不实用 style 插入,应用 link 形式插入
// 'style-loader',
'css-loader',
'postcss-loader',
'less-loader'
]
},
{
test: /\.css$/, // 解析组件外部 style
use: [
MiniCssExtractPlugin.loader,
// 'style-loader',
'css-loader',
'postcss-loader'
]
}
]
},
optimization: {
minimizer: [
new TerserWebpackPlugin({parallel: true// 开启多线程}), // 压缩 js
new OptimizeCssAssetsWebpackPlugin({
cssProcessPluginOptions: {preset: ['default', { discardComments: { removeAll: true} }]
}
}) // 压缩 css
],
// 模块只导出被应用的成员
usedExports: true,
sideEffects: true // 标记无副作用,配合 package js 摇树应用
},
plugins: [new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Vue prod App',
template: path.resolve(__dirname, 'public/index.html'),
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
}),
new MiniCssExtractPlugin({ // 生成 link 的 css 文件, 名称 hash
filename: '[name].[contenthash:8].css'
}),
new CopyWebpackPlugin([ // 复制文件
'public'
]),
new ScriptExtHtmlWebpackPlugin({ // 写入 html 中
inline: [/runtime~.+\.js$/] // 正则匹配 runtime 文件名
}),
new BundleAnalyzerPlugin() // webpack 打包后果剖析]
})
// css 摇树应用 postcss 插件实现了
- 老样子配置模式,和输入地址,这会的文件名称就须要用到 contenthash 文件级别的 hash 生成,不便咱们做浏览器缓存(chunkFilename 文件的名字也做了 hash)。
- 去除 source-map
- 配置 css 相干 loader 最初转换的时候咱们不应用 style-loader 形式用 style 标签插入,而是应用 link 文件生成 css 文件引入
- optimization 配置压缩 js 和 css,usedExports 标识导出应用的模块,标记 sideEffects 无副作用配合 package 文件标识局部右副作用文件,来实现 js 摇树
- 最初就是插件,清空文件夹,html 模板,生成 css 文件(不反对热加载,生产阶段应用,该插件的次要是为了抽离 css 款式, 避免将款式打包在 js 中文件过大和因为文件大网络申请超时的状况), 复制文件,而后把 runtime 结尾的治理 (import()babel) 异步治理导入的文件,写入到 html 中(为什么这样 common 配置里有写,webpack 相干的 runtimeChunk 又讲相干常识)。上线优化计划
- 最初就是增加一个 webpack 后果打包剖析的插件
babel.config.js 咱们有一个兼容[vue-cli 的浏览器兼容 babel 的配置]
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
}
最初贴的就是 package.json
{
"name": "vue-app-base",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.prod.js",
"lint": "eslint --ext .js,.vue src/",
"all-lint-fix": "eslint --fix --ext .js,.vue src/",
"precommit": "lint-staged"
},
"dependencies": {
"core-js": "3",
"vue": "^2.6.11"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.12.7",
"@fullhuman/postcss-purgecss": "^3.0.0",
"@vue/cli-plugin-babel": "^4.5.9",
"babel-loader": "^8.2.1",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.0.4",
"cross-env": "^7.0.3",
"css-loader": "^5.0.1",
"cssnano": "^4.1.10",
"eslint": "^7.14.0",
"eslint-config-standard": "^16.0.2",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-vue": "^7.1.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
"husky": "^4.3.0",
"image-webpack-loader": "^7.0.1",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"lint-staged": "^10.5.2",
"mini-css-extract-plugin": "^1.3.1",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"postcss": "^8.1.10",
"postcss-cssnext": "^3.1.0",
"postcss-import": "^13.0.0",
"postcss-loader": "^4.1.0",
"preload-webpack-plugin": "^3.0.0-beta.4",
"script-ext-html-webpack-plugin": "^2.1.5",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "2.2.1",
"thread-loader": "^3.0.1",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.5",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.41.2",
"webpack-bundle-analyzer": "^4.1.0",
"webpack-cli": "3.3",
"webpack-dev-server": "^3.9.0",
"webpack-merge": "^5.4.0"
},
"lint-staged": {"src/**/*.(js|vue)": [
"eslint --fix",
"git add"
]
},
"husky": {
"hooks": {"pre-commit": "npm run precommit"}
},
"eslintConfig": {
"root": true,
"env": {"node": true},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {"parser": "babel-eslint"},
"rules": {}},
"browserslist": [
"> 1%",
"last 2 versions",
"IE 10"
],
"sideEffects": [
"*.css",
"*.less",
"*.vue"
]
}
这样就配置实现了,这一套配置其实去除掉 vue 相干的就是一套通用配置。
做下 webpack 的 loader 和 plugin 区别
- loader: loader 尽管是扩大了 webpack,然而它只专一于转化文件(transform)这一个畛域,实现压缩,打包,语言翻译。
loader 是运行在 NodeJS 中。
仅仅只是为了打包,仅仅只是为了打包,仅仅只是为了打包,重要 的话说三遍!!! - plugin:plugin 也是为了扩大 webpack 的性能,然而 plugin 是作用于 webpack 自身上的。而且 plugin 不仅只局限在打包,资源的加载上,它的性能要更加丰盛。从打包优化和压缩,到从新定义环境变量,功能强大到能够用来解决各种各样的工作。
webpack 的执行流程咱们能够间接来参考这张图来看。