前面我们说了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(/