初識webpack(三)環(huán)境分離終結(jié)篇(大概吧~~)

呦吼各位,時隔了很久的我終于開始補(bǔ)全配置webpack的教程了,前兩篇講到了幾個常用的插件和loader,那么大家對webpack也有了一個最基本的認(rèn)識,知道了它是如何配置并正常工作的,那么這篇我將通過vue來具體的想大家說明如何根據(jù)環(huán)境進(jìn)行對應(yīng)的操作。(其實(shí)是我不大想使用vue3.0腳手架生成,但是我又想使用webpack4.0....所以就仿照vue2.x腳手架生成的webpack配置,完成webpack4.0的配置)

  • 首先我們需要一些額外的pluginloader現(xiàn)在我貼出我的package.json文件;你可以直接復(fù)制下來然后放到package.json文件中也是可以使用的。
{
  "name": "learnWebpackOfVue",
  "version": "1.0.0",
  "description": "這是一個webpack4.0的vue配置",
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config webpack/webpack.dev.js",
    "start": "npm run dev",
    "build": "node webpack/build.js"
  },
  "author": "Sky <syl1998@hotmail.com>",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.6.10",
    "vue-router": "^3.0.7"
  },
  "devDependencies": {
    "@babel/cli": "^7.5.0",
    "@babel/core": "^7.5.4",
    "@babel/plugin-transform-runtime": "^7.5.0",
    "@babel/preset-env": "^7.5.4",
    "@babel/runtime": "^7.5.4",
    "autoprefixer": "^9.6.1",
    "babel-loader": "^8.0.6",
    "css-loader":"3.2.0",
    "clean-webpack-plugin": "^3.0.0",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.8.0",
    "node-notifier": "^5.4.1",
    "node-sass": "^4.12.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "ora": "^3.4.0",
    "portfinder": "^1.0.21",
    "sass-loader": "^7.1.0",
    "terser-webpack-plugin": "^1.3.0",
    "url-loader": "^2.0.1",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.39.3",
    "webpack-cli": "^3.3.6",
    "webpack-dev-server": "^3.5.1",
    "webpack-merge": "^4.2.2"
  }
}



  • 然后便是文件目錄的結(jié)構(gòu)介紹,既然我們需要根據(jù)環(huán)境進(jìn)行代碼分離,那么我們勢必需要很多的配置文件,現(xiàn)在我貼上我自己的目錄結(jié)構(gòu):
├─config
│      dev.env.js
│      index.js
│      prod.env.js
│
├─dist
│
├─src
│  │  App.vue
│  │  index.html
│  │  main.js
│  │
│  ├─router
│  │      index.js
│  │
│  └─views
│          Helloworld.vue
│
├─webpack
│       build.js
│       utils.js
│       webpack.base.js
│       webpack.dev.js
│       webpack.prod.js
├─.babelrc
├─.postcssrc.js
└─package.json

現(xiàn)在我來介紹下這幾個文件夾,首先是 config 文件夾,該文件夾中存儲著抽離出來需要隨時變更的webpack配置信息,總不能我們老是去變更webpack的配置文件,那樣不僅僅會很麻煩而且很容易改錯,那么我們就將需要經(jīng)常變更的變量抽離出來,放到這里,這里分index,dev,prod三個文件,其中index文件中包含著這些配置信息,我們來看一看都寫了什么:

'use strict'
const path = require('path')

module.exports = {
   // 對應(yīng)dev環(huán)境的快捷設(shè)置
   dev: {
       assetsSubDirectory: 'static',
       assetsPublicPath: '/',
       // 使用webpack進(jìn)行端口代理,一般是用于跨域
       proxyTable: {},
       // 使用什么devtools
       devtool: 'cheap-module-eval-source-map',
       // 這是設(shè)置的是局域網(wǎng)和本地都可以訪問
       host: '0.0.0.0',
       // 端口號
       port: 8080,
       // 是否自動打開瀏覽器
       autoOpenBrowser: false,
       errorOverlay: true,
       // 是否使用系統(tǒng)提示彈出錯誤簡略信息
       notifyOnErrors: true,
       poll: false,
   },
   //  對應(yīng)build環(huán)境的快捷設(shè)置
   build: {
       // 模板index
       index: path.resolve(__dirname, '../dist/index.html'),

       // 此處決定打包的文件夾
       assetsRoot: path.resolve(__dirname, '../dist'),
       assetsSubDirectory: 'static',
       // 針對vue,如果你想通過雙擊index打開你的頁面的話
       // 你就需要更改為'./'即可
       assetsPublicPath: '/',
       
       /**
        * 打包時是否啟用map
        */

       productionSourceMap: false,
       devtool: '#source-map',

       // 此處配置是是否啟動webpack打包檢測你可以通過使用以下命令進(jìn)行啟動
       // `npm run build --report`
       // 或者你也可以直接設(shè)置true或者false來直接進(jìn)行控制
       bundleAnalyzerReport: process.env.npm_config_report,
   }
}

而dev和prod文件則則是這樣的,dev.env.js:

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  // 設(shè)置運(yùn)行環(huán)境
  NODE_ENV: '"development"',
  server_api:'"這里存放著開發(fā)環(huán)境服務(wù)器地址"'
})

prod.env.js:

'use strict'
module.exports = {
  NODE_ENV: '"production"',
  server_api:'"這里存放著生產(chǎn)環(huán)境服務(wù)器地址"'
}

在這里我們可以直接使用不同的開發(fā)環(huán)境進(jìn)行關(guān)鍵詞設(shè)置,然后這里同樣還能存放服務(wù)器地址,這樣的話就可以不同的環(huán)境連接不同的服務(wù)器,就不需要手動去更改了。但是需要在webpack的插件中新增一個webpack的一個插件,稍后會提到。dist文件夾,就不多提了,打包之后的文件都在這里。src文件夾,我們的源碼存放地點(diǎn),因?yàn)槭褂玫氖莢ue,我就仿照vue2.x的腳手架的目錄結(jié)構(gòu)了,這里大家看著做個參考就好。那么重點(diǎn)就是我們的這個webpack文件夾了,這里,存放了webpack的配置信息,讓我們挨個探索~

// 首先是我們的webpack.base文件
const path = require('path');
const utils = require('./utils')
const config = require('../config')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

function resolve(dir) {
    return path.join(__dirname, '..', dir)
}

module.exports = {
    context: path.resolve(__dirname, '../'),
    entry: {
        app: './src/main.js'
    },
    output: {
        filename: utils.assetsPath('js/[name].[chunkhash:8].js'),
        chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].bundle.js'),
        path: config.build.assetsRoot,
        publicPath: process.env.NODE_ENV === 'production'
            ? config.build.assetsPublicPath
            : config.dev.assetsPublicPath
    },
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': resolve('src'),
        }
    },
    module: {
        rules: [{
            test: /\.vue$/,
            use: 'vue-loader'
        },
        {
            test: /\.js$/,
            // ?cacheDirectory=true這個參數(shù),是使用緩存
            // 這樣的好處是當(dāng)你的項(xiàng)目非常大的時候,這將提升至少2倍的構(gòu)建速度
            loader: 'babel-loader?cacheDirectory=true',
            include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
        },
        {
            test: /\.(gif|png|jpe?g|svg)$/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 8192,
                    name: utils.assetsPath('image/[name]-[hash:8].[ext]')
                }
            }]
        },
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 8192,
                    name: utils.assetsPath('fonts/[name]-[hash:8].[ext]')
                }
            }]
        }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
};
  • 在這里我們將公共部分的loader提取出來進(jìn)行,在這里我們也將字體和js以及圖片文件分離開歸類各個文件夾,而VueLoaderPlugin這個插件由于vue2.5以后均需要載入所以我就放在這里了。我們再看看剩下的兩個webpack文件。
// webpack.dev.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const config = require("../config")
const portfinder = require('portfinder')
const utils = require('./utils')
const dev = require('../config/dev.env')
// 這些配置信息是在config文件夾的index中設(shè)置的
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)

const options = merge(common, {
    devtool: config.dev.devtool,
    module: {
        rules: [
          // 由于這里我可能會用到scss,所以就直接添加了sass-loader用來處理
          // 如果你們需要less,stylus預(yù)處理器,就請自行添加吧~規(guī)則在前面有說哦。
            {
                test: /\.css$/,
                use: ['vue-style-loader', 'css-loader']
            },
            {
                test: /\.scss/,
                use: ['vue-style-loader', 'css-loader', 'sass-loader']
            },
        ]
    },
    output: {
        filename: '[name].js',
    },
    devServer: {
        // 日志等級
        clientLogLevel: 'warning',
        // 當(dāng)使用history出現(xiàn)404時則自動調(diào)回index頁
        historyApiFallback: true,
        contentBase: path.join(__dirname, "../dist"),
        // 熱加載模式
        hot: true,
        // 啟用gzip
        compress: false,
        // 設(shè)置webpackdevServer地址
        host: HOST || config.dev.host,
        // 設(shè)置webpackdevServer端口
        port: PORT || config.dev.port,
        // 設(shè)置是否自動打開瀏覽器
        open: config.dev.autoOpenBrowser,
        // 當(dāng)編譯器出現(xiàn)錯誤時,在全屏覆蓋顯示錯誤位置
        overlay: config.dev.errorOverlay
            ? { warnings: false, errors: true }
            : false,
        // 從config文件中讀取端口代理設(shè)置
        proxy: config.dev.proxyTable,
        // 啟用簡潔報錯
        quiet: true,
        // 啟用監(jiān)聽文件變化
        watchOptions: {
            poll: config.dev.poll,
        }
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': dev
        }),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'src/index.html',
            chunksSortMode: 'none',
            inject: 'body',
            hash: true
        }),
        new webpack.HotModuleReplacementPlugin()
    ]
});
// 使用portfinder來檢查端口占用問題,如果發(fā)現(xiàn)端口出現(xiàn)被占用的情況
// 則端口號+1,直到找到可以使用的端口為止,默認(rèn)情況是8000到25565
// 當(dāng)然你還可以自己設(shè)置搜索范圍,詳情請查看:https://www.npmjs.com/package/portfinder

// 這里還使用了FriendlyErrorsPlugin對控制臺輸出信息進(jìn)行簡略友好化輸出。
// 詳情使用請查看:https://www.npmjs.com/package/friendly-errors-webpack-plugin

// 當(dāng)一切準(zhǔn)備就緒就通過Promise.resolve方法拋出處理好的webpack配置信息
// 出現(xiàn)錯誤則是Promise.reject方法拋出錯誤信息供開發(fā)者調(diào)試修改
module.exports = new Promise((resolve, reject) => {
    portfinder.basePort = process.env.PORT || config.dev.port
    portfinder.getPort((err, port) => {
        if (err) {
            reject(err)
        } else {
            process.env.PORT = port
            options.devServer.port = port
            options.plugins.push(new FriendlyErrorsPlugin({
                compilationSuccessInfo: {
                    messages: [`您的項(xiàng)目已成功啟動`],
                    notes: [`本機(jī)訪問請在vscode的終端中按住左ctrl鍵點(diǎn)擊: http://127.0.0.1:${port} \n `, `局域網(wǎng)訪問地址: http://${utils.getNetworkIp()}:${port}`],
                },
                onErrors: config.dev.notifyOnErrors
                    ? utils.createNotifierCallback()
                    : undefined
            }))

            resolve(options)
        }
    })
})
// webpack.prod.js
const path = require('path');
const webpack = require('webpack');
const common = require('./webpack.base.js');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const config = require('../config')
const utils = require('./utils')
const env = require('../config/prod.env')

const options = merge(common, {
    mode: "production",
    devtool: config.build.productionSourceMap ? config.build.devtool : false,
    output: {
        path: config.build.assetsRoot,
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MiniCssPlugin.loader,
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                          // 是否開啟css映射,根據(jù)config文件設(shè)置決定
                            sourceMap: config.build.productionSourceMap
                        }
                    }]
            },
            {
                test: /\.scss$/,
                use: [
                    MiniCssPlugin.loader,
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            sourceMap: config.build.productionSourceMap
                        }
                    },
                    'sass-loader']
            }
        ]

    },
    optimization: {
        splitChunks: {
            chunks: "async",
            cacheGroups: {
                vendor: { 
                    // 將第三方模塊提取出來
                    minSize: 30000,
                    minChunks: 1,
                    test: /node_modules/,
                    chunks: 'initial',
                    name: 'vendor',
                    priority: 1
                },
                commons: {
                    test: /[\\/]src[\\/]common[\\/]/,
                    name: 'commons',
                    minSize: 30000,
                    minChunks: 3,
                    chunks: 'initial',
                    priority: -1,
                    // 這個配置允許我們使用已經(jīng)存在的代碼塊
                    reuseExistingChunk: true 
                }
            }
        },
        runtimeChunk: { name: 'runtime' },
        minimizer: [
            new TerserPlugin({
                test: /\.js(\?.*)?$/i,
                // 是否開啟多線程
                parallel: true,
                // 是否開啟映射
                sourceMap: config.build.productionSourceMap,
                terserOptions: {
                    warnings: false,
                    // 去除打印
                    compress: {
                        warnings: false,
                        drop_console: true,
                        drop_debugger: true,
                        pure_funcs: ['console.log']
                    },
                    // 去除注釋,當(dāng)設(shè)置為true時,會保留注釋
                    // 當(dāng)然這個默認(rèn)是false
                    output: {
                        comments: false,
                    },
                }
            }),
            new OptimizeCssAssetsPlugin({
                assetNameRegExp: /\.css$/g,
                cssProcessorOptions: {
                    safe: true,
                    autoprefixer: { disable: true },
                    mergeLonghand: false,
                    discardComments: {
                        // 移除css中的注釋
                        removeAll: true 
                    }
                },
                canPrint: true
            })
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': env
        }),
        new CleanWebpackPlugin(),
        new MiniCssPlugin({
            filename: utils.assetsPath('css/[name].css'),
            chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
        }),
        new webpack.HashedModuleIdsPlugin(),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: 'src/index.html',
            inject: 'body',
            chunksSortMode: 'none',
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                minifyJS: true,
                minifyCSS: true
            }
        }),
    ],
    stats: {
        // 顯示所有模塊
        maxModules: Infinity,
        // 顯示模塊為何被引入
        reasons: true,
    }
});
// 當(dāng)config中對應(yīng)項(xiàng)為true時,啟用打包分析
if (config.build.bundleAnalyzerReport) {
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    prodWebpackConf.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = options;
  • 那么在這兩個文件中你們都看到了utils文件,而在package.json中打包命令執(zhí)行的卻是一個build.js而不是webpack.prod.js現(xiàn)在,我就來隆重的介紹這兩個工具文件,他們是美化日志輸出的小幫手,即使是敲代碼,我們也要干干凈凈的看log,控制臺要整潔對于錯誤和警告都要一目了然~話不多說,上代碼
//  utils.js
'use strict'
const path = require('path')
const packageConfig = require('../package.json')
const config = require('../config')

// 將資源文件合并到config文件中的配置項(xiàng)中
exports.assetsPath = function (_path) {
    const assetsSubDirectory = process.env.NODE_ENV === 'production'
        ? config.build.assetsSubDirectory
        : config.dev.assetsSubDirectory

    return path.posix.join(assetsSubDirectory, _path)
}
// 當(dāng)出現(xiàn)報錯時,使用node-notifier彈出系統(tǒng)消息彈窗告知出現(xiàn)錯誤
exports.createNotifierCallback = () => {
    const notifier = require('node-notifier')

    return (severity, errors) => {
        if (severity !== 'error') return

        const error = errors[0]
        const filename = error.file && error.file.split('!').pop()
        // 這里是設(shè)置當(dāng)出現(xiàn)錯誤時,給你的,標(biāo)題,錯誤信息,錯誤文件地址,以及圖標(biāo)
        notifier.notify({
            title: packageConfig.name,
            message: severity + ': ' + error.name,
            subtitle: filename || '',
            icon: path.join(__dirname, 'logo.png')
        })
    }
}

// 獲取本地局域網(wǎng)ip
// 這里也就是為何我在config文件中設(shè)置了host為0.0.0.0
// 這樣的話除了本地開發(fā)可預(yù)覽,和開發(fā)機(jī)在同一個局域網(wǎng)訪問
// 該計算機(jī)ip和端口一樣可以訪問,靈感來自react的控制臺。
exports.getNetworkIp = () => {
    const os = require('os');
    let needHost = ''; // 打開的host
    try {
        // 獲得網(wǎng)絡(luò)接口列表
        let network = os.networkInterfaces();
        for (let dev in network) {
            let iface = network[dev];
            for (let i = 0; i < iface.length; i++) {
                let alias = iface[i];
                if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                    needHost = alias.address;
                }
            }
        }
    } catch (e) {
        needHost = 'localhost';
    }
    return needHost;
}
// build.js
'use strict'
process.env.NODE_ENV = 'production'

const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod')
// 設(shè)置進(jìn)度條文字
const spinner = ora('正在打包...')
// 開啟進(jìn)度條
spinner.start()

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {
    // webpack執(zhí)行打包操作之后關(guān)閉進(jìn)度條
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      // 開啟控制臺顏色輸出
      colors: true,
      // 是否顯示打包了源碼文件
      modules: false,
      // 如果你是typescript,開啟此項(xiàng)則會導(dǎo)致報錯
      children: false, 
      // 后面這兩個個人感覺和開啟了modules沒太大區(qū)別,只是顯示更加簡略一些
      // 是否顯示生成的文件塊
      chunks: false,
      // 顯示打包的代碼塊來源
      chunkModules: false
    }) + '\n\n')
    // 當(dāng)打包失敗時
    if (stats.hasErrors()) {
      console.log(chalk.red('  打包失敗\n'))
      process.exit(1)
    }
    // 當(dāng)打包成功時
    console.log(chalk.cyan(' 打包成功\n'))
  })
})
  • 那么大家看明白了嗎?以上代碼很多都是來自vue2.x腳手架,因?yàn)槲矣X得這些東西比腳手架3.0的控制臺更加直觀明了,但是我還是非常非常的喜歡V3的ui功能,相比控制臺更加的美觀,但是卻不夠方便,現(xiàn)在我們還差兩樣?xùn)|西,在配置文件中我們配置了babel和postcss看過前兩個文章的小伙伴一定清楚,這兩個小家伙同樣也需要配置信息,那么廢話不多說,上代碼!
// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }]
  ],
  "plugins": ["@babel/transform-runtime"]
}

此處你們會發(fā)現(xiàn)和上兩個文章均不同,這里要注意啦?。ㄇ煤诎澹┰谶@里我直接升級了babel的版本使用的是版本7,那么以前的那一套就全部被廢棄,現(xiàn)在的env就需要使用上面的配置而無需 babel-preset-stage-x 這個插件了,而 runtime 則也是換成了對應(yīng)的版本。

// .postcssrc.js
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
  "plugins": {
    "postcss-import": {},
    "postcss-url": {},
    // to edit target browsers: use "browserslist" field in package.json
    "autoprefixer": {}
  }
}

postcss則沒有任何變化還是和之前一樣

至此,針對webpack的配置就告一段落了,稍后我會放上我這個示例的最后完成文件,當(dāng)然是針對vue的,因?yàn)樽罱恢币苍趯W(xué)習(xí)vue啦~~~ 如果文檔有落后的地方我會及時更新的。嘻嘻,那么我們webpack5.0見~

附上工程: 提取碼:v50a

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容