超完美的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處理的文件

項目結構

先說下我的項目結構
在這裏插入圖片描述
經過我們的一致討論,決定還是把每個頁面都單獨弄成一個文件夾,然後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
}

具體情況,看你自己的項目,自己做調整就好
下面我貼下我的配置文件

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一下
在這裏插入圖片描述
在這裏插入圖片描述
而且必須下載到這裏才能行,如上圖所示,對了,還有個事,以下圈起來的兩個
在這裏插入圖片描述在這裏插入圖片描述
這兩個是我們使用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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章