超完美的webpack4多页面开发环境来了!

前情提要:

我之前配过两次webpack多页面,gulp+webpack多页面,后来经过项目实践,我发现gulp+webpack那一版还能凑合用,但是之前配置的那一版纯webpack版本的简直难用的要死,进群的小伙伴大部分也是问这个问题的,所以我痛定思痛,重新温习了下webpack基础,通过一个星期的努力,终于配置出了我自己认为很完美的纯webpack4的多页面的开发环境,以前的配置多页面博客我会删除,免得误导大家!!!!

结尾我会附上我这个模板的码云地址

下面说下配置思路

  1. 配置共分4个文件,一个入口文件,一个基础配置文件,一个开发环境文件,一个生产环境文件,足矣
  2. 可以支持ES6语法,还要支持async await,Class等高级语法,还要支持includes这种语法会被转译
  3. 可以支持scss语法,生成css文件后,自动加浏览器css后缀
  4. html可以处理公共的头部和尾部,html去空格,去掉属性的双引号
  5. 可以自动处理图片,小的图片自动处理成base64,大点的通过url-loader处理,打包到目标文件夹下
  6. 我们是根据项目需求,把url全部抽离出来了,当然你也可以不抽离
  7. 开发,测试,部署请求地址分开,基于第6项
  8. 公共的js抽离出来,scss文件引入js后,可以单独拉出来形成css文件,js生产环境下压缩混淆,css压缩
  9. 清除dos窗口的一些打包出来的信息,开发时类似vue-cli脚手架,打包完了提示项目在哪里运行
  10. 把首页index.html单独抽离出来放在服务器的最外边,这样就可以通过域名直接访问首页
  11. 默认端口号一旦被占用,自动寻找下一个能用的端口号
  12. js我是全部下载的min.js通过公共的html引入的,当然你在实际项目中也可以import引入,这都不影响
  13. assets文件夹下只放webpack能处理的文件,static放一些静态文件,不会被webpack处理的文件

项目结构

先说下我的项目结构
超完美的webpack4多页面开发环境来了!_第1张图片
经过我们的一致讨论,决定还是把每个页面都单独弄成一个文件夹,然后html,js,scss文件全部放到自己的文件夹下,在src同级目录下会有个static文件夹,把一些静态文件,不需要webpack处理的文件可以放在里面,这样这个文件夹会原封不动的复制到目标文件夹下
超完美的webpack4多页面开发环境来了!_第2张图片
就像这样,下面先说下多页面处理多入口的处理办法

多入口处理

一般人会选择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
}

如果你的项目结构是这样的,那么入口这样的

超完美的webpack4多页面开发环境来了!_第3张图片超完美的webpack4多页面开发环境来了!_第4张图片超完美的webpack4多页面开发环境来了!_第5张图片
把文件都拆成三个文件夹了,那么你的入口应该是这个样子的

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
}

具体情况,看你自己的项目,自己做调整就好
下面我贴下我的配置文件

webpack.base.js

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));
});

webpack.dev.js

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);
        }
    })
})

webpack.prod.js

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一下
超完美的webpack4多页面开发环境来了!_第6张图片
超完美的webpack4多页面开发环境来了!_第7张图片
而且必须下载到这里才能行,如上图所示,对了,还有个事,以下圈起来的两个
超完美的webpack4多页面开发环境来了!_第8张图片超完美的webpack4多页面开发环境来了!_第9张图片
这两个是我们使用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的脚手架,哈哈!

此模板码云地址:webpack4多页面配置开发模板https://gitee.com/zhaoxiang688/webpack4-vue-multipage

点击上述超链接即可访问

最后:

微信公众号:三哥玩前端

QQ交流群:859708141

你可能感兴趣的:(JavaScript,Webpack打包工具,webpack4,webpack4多页面,webpack4多页面开发环境,nodejs,javascript)