前面我们说了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的执行流程我们可以直接来参考这张图来看。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。