为什么
因为项目是用glpu搭建的工作流,在转Jenkins自动发布的时候,考虑到如果需要多页面动态打包,需要改写glup配置(不会glup也不想学),就将项目重构为webpack4.
遇到的难点
- 扫描项目多页面入口
- 区分生产和开发环境webpck配置
- 指定项目打包
- 使用shell指令进行打包操作
- 如何处理不同的模块模式
- 修改html如何触发热更新
- 热更新的原理
- 区分生产和开发环境的懒加载
解决方式
1. 扫描项目多页面入口
引入glob第三方插件(大佬说fs模块自带扫描,表示懵逼没找到),
//获得一个动态的目录
const glob = require('glob')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
//import glob from "glob"
var obj = {
detail: {},
entry: {
}
}
const htmlWebpackPluginDevConfig = { plugins: [] }
//${process.env.ENV_file}用来区别项目
glob.sync(`./src/${process.env.ENV_file}/**/*.html`).forEach(e => { //同步
let arr = e.split('/')
function getDir(arr) {
let arr1 = JSON.parse(JSON.stringify(arr))
arr1.splice(-1)
return arr1
}
const chunk = arr[arr.length - 1].split('.')[0]
const filename = chunk + '.html'
//这个是入口的详细情况,以后会用到
obj['detail'][chunk] = {
name: chunk,
path: e,
dir: path.resolve(__dirname, `${getDir(arr).join('/')}`)
}
//entry是暴露出去的入口文件
obj['entry'][chunk] = `${getDir(arr).join('/')}` + '/main.js'
const htmlConf = {
filename: filename,
template: e,
//favicon: './src/assets/images/favicon.ico',
inject: 'body',
chunks: [chunk]
}
//新建htmlWebpackPluginDevConfig对象,输出html文件
htmlWebpackPluginDevConfig.plugins.push(new HtmlWebpackPlugin(htmlConf))
})
module.exports = { obj, htmlWebpackPluginDevConfig }
2. 区分生产和开发环境
将公共配置抽离出来成为webpack.common.js//webpack.common.js
const util = require('./util')//用来获得entry和htmlWebpackPluginDevConfig
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
entry: util.obj.entry,
plugins: [
new ExtractTextPlugin('style/[name]_main[hash].css'),
new CleanWebpackPlugin([`../dist/${process.env.ENV_file}`],
{
allowExternal: true //允许删除根目录以外的文件夹
}),
...util.htmlWebpackPluginDevConfig.plugins,
new CopyWebpackPlugin([{
from: path.resolve(__dirname, `../src/${process.env.ENV_file}/static`)
}]),
],
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader']
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
// use: ["css-loader", 'postcss-loader']
use: [{
loader: 'css-loader',
}, {
loader: 'postcss-loader'
}
]
}),
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components|dependencies)/,
use: [{
loader: 'babel-loader',
},
{
loader: path.resolve("./inject-loader.js") // 开发模式使用注入代码实现html热更新
}
]
}
]
},
output: {
path: path.resolve(__dirname, `../dist/${process.env.ENV_file}`),
filename: 'js/[name].bundle[hash].js',
chunkFilename: 'help.bundle.js',
}
};
引入webpack-merge进行配置合并(类似于object.assign) 以dev为例,于pro相比,取消了代码压缩,增加了开发服务器和热更新
//webpack.dev.js
const path = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin')
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
// devtool: 'inline-source-map',
devServer: {
contentBase: `./dist/${process.env.ENV_file}`,
hot: true
},
plugins: [
new webpack.NamedModulesPlugin(),//以便更容易查看要修补(patch)的依赖
new webpack.HotModuleReplacementPlugin(),
new CopyWebpackPlugin([{
from: path.resolve(__dirname, `../src/${process.env.ENV_file}/static`)
}])
],
optimization: {
},
module: {
rules: [
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: [':data-src']
}
}
},
]
},
});
3. 指定项目打包
引入cross-env(这个包是能够跨平台引入全局对象的包,可在webpack配置和开发代码中引用,我很喜欢这个插件) 例如在package.json中的进行ENV_file进行写死赋值(后期是通过shell命令动态赋值)
然后就可以在任何一个地方使用process.env.ENV_file引用该变量//获得一个动态的目录
const glob = require('glob')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
//import glob from "glob"
var obj = {
detail: {},
entry: {
}
}
const htmlWebpackPluginDevConfig = { plugins: [] }
glob.sync(`./src/${process.env.ENV_file}/**/*.html`).forEach(e => { //同步
let arr = e.split('/')
function getDir(arr) {
let arr1 = JSON.parse(JSON.stringify(arr))
arr1.splice(-1)
return arr1
}
const chunk = arr[arr.length - 1].split('.')[0]
const filename = chunk + '.html'
obj['detail'][chunk] = {
name: chunk,
path: e,
dir: path.resolve(__dirname, `${getDir(arr).join('/')}`)
}
obj['entry'][chunk] = `${getDir(arr).join('/')}` + '/main.js'
const htmlConf = {
filename: filename,
template: e,
//favicon: './src/assets/images/favicon.ico',
inject: 'body',
chunks: [chunk]
}
htmlWebpackPluginDevConfig.plugins.push(new HtmlWebpackPlugin(htmlConf))
})
module.exports = { obj, htmlWebpackPluginDevConfig }
4. 使用shell指令的时候,调用开发本地库而不是全局库
项目需要上Jenkins自动发布,因此打包脚本要改为动态打包
//build.sh
dir=$(ls -l ./src |awk '/^d/ {print $NF}')
cur_date="`date +%Y%m%d`"
for i in $dir
do
npx cross-env ENV_file=${i} webpack --config ./build/webpack.pro.js
echo "打包的文件夹是: $i"
done
cd dist
tar -zcvf ../sparrow_${cur_date}.tar.gz ./*
echo "打包完毕""
使用shell获得文件夹目录,使用for循环动态执行webpack命令,其中npx是npm(5.2以上)自带的一个包,可用来执行本地指令(与package中scripts类似的作用),这里有对 cross-env进行动态赋值的操作
5. 如何处理不同的模块模式
原先项目使用的是src引入的第三方依赖,会将变量暴露在window下 而模块化本地打包之后,会通过require暴露一个对象需要手动在全局注册一下。
6. 修改html如何触发热更新
手动写一个inject-loader,在dev环境下使用该loader,在pro就不用了
//webpack.dev
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: [':data-src']
}
}
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components|dependencies)/,
use: [{
loader: 'babel-loader',
},
{
loader: path.resolve("./inject-loader.js") // 开发模式使用注入代码实现html热更新
}
]
}
其中source是webpack内置的一个参数,其中会返回上一级loader返回的字符串,然后你要做得就是在其前面拼接一个html模板的字符串,然后将其返回给下一级
//inject-loader
const path = require("path");
module.exports = function (source) {
var file = path.basename(this.resourcePath)
var dir = this.resourcePath.split('/')[this.resourcePath.split('/').length - 2]
console.log('------------------------------------', file, dir)
if (file === "main.js" && dir !== 'scripts') {
return `if (process.env.NODE_ENV === "development") {
require("./${dir}.html");
};` + source;
}
return source
}
7. 热更新的原理
这里我也迷了很久,后来才知道大致和dom事件差不多,都是采用的冒泡形式,自变更的那个文件,冒泡到引用该文件的最上层,vue-loader,css-loader都有对其变化进行拦截,关键点在module.hot.accept这个函数,一般不会出现在业务代码中,会出现在loader源码中。 推荐一个 大佬的github进行尾随学习
8. babel配置
开发环境,不需要有懒加载,懒加载只在生产上面用
//.babelrc
{
"env": {
"development": {
"plugins": ["dynamic-import-node"]
}
}
}
- dynamic-import-node这个插件是用来将import()转化为require(),在生产环境用代码分割,在开发环境使用同步引入,可以加快热更新的速度
- useBuiltIns这个option可以在入口处引入babel-polyfill之后进行按需补丁
- targets可以选择编译后的版本类型
其他
啊~虽然学的很累,但是不会说学不动了,大佬是灯塔,就算我看不见,也有人会需要