我之前配过两次webpack多页面,gulp+webpack多页面,后来经过项目实践,我发现gulp+webpack那一版还能凑合用,但是之前配置的那一版纯webpack版本的简直难用的要死,进群的小伙伴大部分也是问这个问题的,所以我痛定思痛,重新温习了下webpack基础,通过一个星期的努力,终于配置出了我自己认为很完美的纯webpack4的多页面的开发环境,以前的配置多页面博客我会删除,免得误导大家!!!!
下面说下配置思路
先说下我的项目结构
经过我们的一致讨论,决定还是把每个页面都单独弄成一个文件夹,然后html,js,scss文件全部放到自己的文件夹下,在src同级目录下会有个static文件夹,把一些静态文件,不需要webpack处理的文件可以放在里面,这样这个文件夹会原封不动的复制到目标文件夹下
就像这样,下面先说下多页面处理多入口的处理办法
一般人会选择glob这个npm包来遍历我们的文件夹,但是我喜欢用nodejs的原生的fs文件系统,整体思路就是先把所以页面的js文件读出来,形成一个入口对象,然后再把所有页面的html文件读出来,形成一个可以被html-webpack-plugin这个插件给处理的一个html数组,然后再通过这个插件形成对应的html文件
const fs = require('fs');
const path = require('path');
function readDir(url){
return fs.readdirSync(path.resolve(__dirname,url));
}
const files = readDir('../src/pages').filter(item => item != 'include');
let htmlArray = [];
let entries = {};
files.forEach(item => {
let result = readDir(`../src/pages/${item}`);
result.forEach(val => {
let extname = path.extname(val).slice(1);
switch(extname){
case 'html':
if(val == 'index.html'){
htmlArray.push({
template:path.resolve(__dirname,`../src/pages/${item}/${val}`),
filename:`${val}`,
chunks:result.length == 3?[val.split('.')[0]]:[],
minify:{
removeAttributeQuotes:true,//删除html属性的双引号
collapseWhitespace:true,//折叠空行,把所有的html折叠成一行
}
});
}else{
htmlArray.push({
template:path.resolve(__dirname,`../src/pages/${item}/${val}`),
filename:`pages/${item}/${val}`,
chunks:result.length == 3?[val.split('.')[0]]:[],
minify:{
removeAttributeQuotes:true,//删除html属性的双引号
collapseWhitespace:true,//折叠空行,把所有的html折叠成一行
}
});
}
break;
case 'js':
entries[val.split('.')[0]] = path.resolve(__dirname,`../src/pages/${item}/${val}`);
break;
}
})
});
module.exports = {
entries,
htmlArray
}
const fs = require('fs');
const path = require('path');
let files = fs.readdirSync(path.resolve(__dirname,'../src/js'));
let htmlFiles = fs.readdirSync(path.resolve(__dirname,'../src/pages'))
htmlFiles = htmlFiles.filter(item => item.indexOf('.html') != -1);
let entries = {};
let htmlArray = [];
files.forEach(item => {
entries[item.split('.')[0]] = [path.resolve(__dirname,`../src/js/${item}`)];
});
htmlFiles.forEach(val => {
let arr = files.filter(item => val.split('.')[0] == item.split('.')[0]);
if(arr.length > 0){
htmlArray.push({
template:path.resolve(__dirname,`../src/pages/${arr[0].split('.')[0]}.html`),
filename:`pages/${arr[0].split('.')[0]}.html`,
chunks:['vendors','common',arr[0].split('.')[0]],
minify:{
removeAttributeQuotes:true,//删除html属性的双引号
collapseWhitespace:true,//折叠空行,把所有的html折叠成一行
}
})
}else{
htmlArray.push({
template:path.resolve(__dirname,`../src/pages/${val.split('.')[0]}.html`),
filename:`pages/${val.split('.')[0]}.html`,
chunks:[],
minify:{
removeAttributeQuotes:true,//删除html属性的双引号
collapseWhitespace:true,//折叠空行,把所有的html折叠成一行
}
})
}
})
module.exports = {
entries,
htmlArray
}
具体情况,看你自己的项目,自己做调整就好
下面我贴下我的配置文件
const path = require('path');
const entry = require('./entries');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const autoprefixer = require('autoprefixer');
const copyWebpackPlugin = require('copy-webpack-plugin');
const targetPath = 'dist';
function getProjectAbsolutePath(dir) {
return path.resolve(__dirname, '../', dir)
}
module.exports = {
entry:entry.entries,
output:{
filename:'pages/[name]/[name].js',
path:path.resolve(__dirname,`../${targetPath}`),
publicPath:'/'
},
resolve:{
extensions:['.js','.scss','css','.json','.vue'],
alias:{//别名
'vue$':'vue/dist/vue.esm.js',
'@': path.join(__dirname, '../src')
},
},
plugins:[
new copyWebpackPlugin({
patterns: [
{
from:'static',
to:'static'
}
]
}),
new MiniCssExtractPlugin({
filename:'pages/[name]/[name].css'
})
],
optimization:{
splitChunks:{//分割代码块
cacheGroups:{//缓存组
common:{//公共的模块
name:'common',
chunks:'initial',
reuseExistingChunk: true,
minSize:0,
minChunks:2
},
vendors:{
name:"vendors",
priority:1,
test:/node_modules/,
chunks:'initial',
reuseExistingChunk: true,
minSize:0,
minChunks:2
}
}
}
},
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,//排除哪个文件夹
include:getProjectAbsolutePath('src'),//包括哪个文件夹
use:[
{
loader:'babel-loader',
options:{//用babel-loader需要把es6转换成es5
"presets":[
['@babel/preset-env',
{
targets: {
ie: "9",
edge: "17", // 程序支持支持 Edge 17
firefox: "50", // 程序支持支持 firefox 60
chrome: "60", // 程序支持支持 chrome 67
safari: "10" // 程序支持safari 11.1
// 支持的配置有以下: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron.
},
useBuiltIns: "usage", //按需注⼊
corejs:2
}
]
],
}
}
]
},
{
//可以处理scss文件, node-sass,sass-loader
test:/\.css$/,
use:[
{
loader:MiniCssExtractPlugin.loader
},
'css-loader',
{
loader: "postcss-loader",
options: {
plugins: [
autoprefixer({
overrideBrowserslist: ["defaults","> 1%","last 10 versions","Firefox < 20","not ie <= 8","ios > 7","cover 99.5%"]
})
]
}
}
]
},
{
test:/\.scss$/,
use:[
{
loader:MiniCssExtractPlugin.loader
},
'css-loader',//@import语法解析路径
{
loader: "postcss-loader",
options: {
plugins: [
autoprefixer({
overrideBrowserslist: ['ie >= 8','Firefox >= 20', 'Safari >= 5', 'Android >= 4','Ios >= 6', 'last 4 version']
})
]
}
},
'sass-loader'//把scss -> css
]
},
{
test:/\.html$/,
use:'html-withimg-loader'
},
{
test:/\.(jpg|png|gif|svg)$/,
use:[
{
loader:"url-loader",
//当图片小于多少k的时候用base64转化
options:{
limit:10*1024,
esModule: false,
outputPath: 'assets/imgs'
}
}
]
}
]
}
}
entry.htmlArray.forEach(element => {
module.exports.plugins.push(new HtmlWebpackPlugin(element));
});
const {smart} = require('webpack-merge');
const webpack = require('webpack');
const base = require('./webpack.base.js');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const portfinder = require('portfinder');
const os = require('os');
///////////////////获取本机ip///////////////////////
function getIPAdress() {
var interfaces = os.networkInterfaces();
for (var devName in interfaces) {
var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {
var alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
return alias.address;
}
}
}
}
const Host = getIPAdress();
const path = require('path')
const fs = require('fs');
const data = fs.readFileSync(path.resolve(__dirname,'./测试.js'),'utf8');
let lines = data.split('\n');
let indexs = [];
lines.forEach((item,index) => {
item = item.replace('\r','');
if(item.indexOf('`') != -1){
indexs.push(index)
}
})
let urls = ''
for(let i = indexs[0]; i <= indexs[1];i++){
urls += (lines[i].trim());
}
urls = JSON.parse(urls.replace(/`/gi,''));
let url = urls.data.filter(item => item.name == 'URL')[0].value;
const devWebpackConfig = smart(base,{
mode:'development',
devtool:'eval-source-map',
devServer:{
contentBase:false,//启动目录
compress:true,//gzip压缩
overlay: {
warnings: false,
errors: true
},
host:'0.0.0.0',
historyApiFallback: true,
disableHostCheck:true,
quiet:true,
proxy: {
'/api': {
target: url,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
},
plugins:[
new webpack.HotModuleReplacementPlugin(),//热更新插件
new webpack.NamedModulesPlugin()
],
});
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = 8080;
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
devWebpackConfig.devServer.port = port;
devWebpackConfig.plugins.push(new FriendlyErrorsWebpackPlugin({
compilationSuccessInfo: {
messages: [`项目模板运行在这里 : http://${Host}:${port}`],
},
onErrors: function(severity,errors){
const notifier = require('node-notifier');
if (severity !== 'error') return;
notifier.notify({
title: '小笨蛋',
message: '运行出错啦,快去看看错误提示吧'
})
}
}))
resolve(devWebpackConfig);
}
})
})
const {smart} = require('webpack-merge');
const base = require('./webpack.base.js');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin')
module.exports = smart(base,{
mode:'production',
devtool:false,
optimization:{
minimizer:[
new TerserPlugin(),
new OptimizeCSSAssetsPlugin({})
]
},
plugins:[
new CleanWebpackPlugin()
],
})
值得一提的是,配置了这里了之后,如下图所示,就可以不用在每个入口引入@babel/polyfill文件了,但是这个@babel/polyfill必须得npm i一下
而且必须下载到这里才能行,如上图所示,对了,还有个事,以下圈起来的两个
这两个是我们使用arcgis做地图用的,如果你没有arcgis地图业务,可以把这两个删除,最后再贴一个抽取公共方法的配置
optimization: {
splitChunks: {
chunks: 'all',//拆分代码类型(同步/异步/同步+异步)
minSize: 30000,//引入的库大小大于30000个字节(30kb)才回去做代码分割
maxSize: 0,////对超出的大小进行二次拆分(一般不配置)
minChunks: 1,//当一个模块被用了至少多少次才进行代码分割
maxAsyncRequests: 6,//同时加载的模块数最多是六个,也就是说,如果我引入了很多库,最多拆分出六个,多了的就不进行拆分了
maxInitialRequests: 4,//入口文件进行代码分割最多引入四个
automaticNameDelimiter: '~',//文件生成的时候的连接符
name:true,//使cacheGroups中配置的文件名称有效
cacheGroups: {//缓存组:符合条件的先缓存着,当所有的文件都分析完成之后,在开启拆分
vendors: {//会将node_modules中所有的依赖拆分到filename的文件中
test: /[\\/]node_modules[\\/]/,
priority: -10,//值越大优先级越高(优先匹配)
filename:'js/[name].js'//自定义打包出来的 文件路径和名称
},
default: {//会将自己定义的一些方法拆分到这个缓存组中(所有的模块都符合default的要求)
minChunks: 2,//当一个模块被用了至少多少次才进行代码分割
priority: -20,
reuseExistingChunk: true,//如果一个模块已经被打包过了,就不会再次被打包了,而是回去找打包的文件
filename:'js/[name].js'
}
}
}
}
项目如何启动在README.md文件中都又详细说明,如果有问题可以加入我们的交流群来提问!!!
下面我贴上一张运行起来的图
是不是很熟悉,对,我就是模仿的vue-cli的脚手架,哈哈!
点击上述超链接即可访问