初识webpack(三)环境分离终结篇(大概吧~~)

呦吼各位,时隔了很久的我终于开始补全配置webpack的教程了,前两篇讲到了几个常用的插件和loader,那么大家对webpack也有了一个最基本的认识,知道了它是如何配置并正常工作的,那么这篇我将通过vue来具体的想大家说明如何根据环境进行对应的操作。(其实是我不大想使用vue3.0脚手架生成,但是我又想使用webpack4.0....所以就仿照vue2.x脚手架生成的webpack配置,完成webpack4.0的配置)
  • 首先我们需要一些额外的pluginloader现在我贴出我的package.json文件;你可以直接复制下来然后放到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 ", "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" } }

  • 然后便是文件目录的结构介绍,既然我们需要根据环境进行代码分离,那么我们势必需要很多的配置文件,现在我贴上我自己的目录结构:
├─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

现在我来介绍下这几个文件夹,首先是 config 文件夹,该文件夹中存储着抽离出来需要随时变更的webpack配置信息,总不能我们老是去变更webpack的配置文件,那样不仅仅会很麻烦而且很容易改错,那么我们就将需要经常变更的变量抽离出来,放到这里,这里分index,dev,prod三个文件,其中index文件中包含着这些配置信息,我们来看一看都写了什么:
'use strict' const path = require('path')module.exports = { // 对应dev环境的快捷设置 dev: { assetsSubDirectory: 'static', assetsPublicPath: '/', // 使用webpack进行端口代理,一般是用于跨域 proxyTable: {}, // 使用什么devtools devtool: 'cheap-module-eval-source-map', // 这是设置的是局域网和本地都可以访问 host: '0.0.0.0', // 端口号 port: 8080, // 是否自动打开浏览器 autoOpenBrowser: false, errorOverlay: true, // 是否使用系统提示弹出错误简略信息 notifyOnErrors: true, poll: false, }, //对应build环境的快捷设置 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打包检测你可以通过使用以下命令进行启动 // `npm run build --report` // 或者你也可以直接设置true或者false来直接进行控制 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, { // 设置运行环境 NODE_ENV: '"development"', server_api:'"这里存放着开发环境服务器地址"' })

prod.env.js:
'use strict' module.exports = { NODE_ENV: '"production"', server_api:'"这里存放着生产环境服务器地址"' }

【初识webpack(三)环境分离终结篇(大概吧~~)】在这里我们可以直接使用不同的开发环境进行关键词设置,然后这里同样还能存放服务器地址,这样的话就可以不同的环境连接不同的服务器,就不需要手动去更改了。但是需要在webpack的插件中新增一个webpack的一个插件,稍后会提到。dist文件夹,就不多提了,打包之后的文件都在这里。src文件夹,我们的源码存放地点,因为使用的是vue,我就仿照vue2.x的脚手架的目录结构了,这里大家看着做个参考就好。那么重点就是我们的这个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这个参数,是使用缓存 // 这样的好处是当你的项目非常大的时候,这将提升至少2倍的构建速度 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提取出来进行,在这里我们也将字体和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中设置的 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预处理器,就请自行添加吧~规则在前面有说哦。 { 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', // 当使用history出现404时则自动调回index页 historyApiFallback: true, contentBase: path.join(__dirname, "../dist"), // 热加载模式 hot: true, // 启用gzip compress: false, // 设置webpackdevServer地址 host: HOST || config.dev.host, // 设置webpackdevServer端口 port: PORT || config.dev.port, // 设置是否自动打开浏览器 open: config.dev.autoOpenBrowser, // 当编译器出现错误时,在全屏覆盖显示错误位置 overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, // 从config文件中读取端口代理设置 proxy: config.dev.proxyTable, // 启用简洁报错 quiet: true, // 启用监听文件变化 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来检查端口占用问题,如果发现端口出现被占用的情况 // 则端口号+1,直到找到可以使用的端口为止,默认情况是8000到25565 // 当然你还可以自己设置搜索范围,详情请查看:https://www.npmjs.com/package/portfinder// 这里还使用了FriendlyErrorsPlugin对控制台输出信息进行简略友好化输出。 // 详情使用请查看:https://www.npmjs.com/package/friendly-errors-webpack-plugin// 当一切准备就绪就通过Promise.resolve方法抛出处理好的webpack配置信息 // 出现错误则是Promise.reject方法抛出错误信息供开发者调试修改 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: [`您的项目已成功启动`], notes: [`本机访问请在vscode的终端中按住左ctrl键点击: http://127.0.0.1:${port} \n `, `局域网访问地址: 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映射,根据config文件设置决定 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, // 这个配置允许我们使用已经存在的代码块 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'] }, // 去除注释,当设置为true时,会保留注释 // 当然这个默认是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, } }); // 当config中对应项为true时,启用打包分析 if (config.build.bundleAnalyzerReport) { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin prodWebpackConf.plugins.push(new BundleAnalyzerPlugin()) } module.exports = options;

  • 那么在这两个文件中你们都看到了utils文件,而在package.json中打包命令执行的却是一个build.js而不是webpack.prod.js现在,我就来隆重的介绍这两个工具文件,他们是美化日志输出的小帮手,即使是敲代码,我们也要干干净净的看log,控制台要整洁对于错误和警告都要一目了然~话不多说,上代码
//utils.js 'use strict' const path = require('path') const packageConfig = require('../package.json') const config = require('../config')// 将资源文件合并到config文件中的配置项中 exports.assetsPath = function (_path) { const assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectoryreturn path.posix.join(assetsSubDirectory, _path) } // 当出现报错时,使用node-notifier弹出系统消息弹窗告知出现错误 exports.createNotifierCallback = () => { const notifier = require('node-notifier')return (severity, errors) => { if (severity !== 'error') returnconst error = errors[0] const filename = error.file && error.file.split('!').pop() // 这里是设置当出现错误时,给你的,标题,错误信息,错误文件地址,以及图标 notifier.notify({ title: packageConfig.name, message: severity + ': ' + error.name, subtitle: filename || '', icon: path.join(__dirname, 'logo.png') }) } }// 获取本地局域网ip // 这里也就是为何我在config文件中设置了host为0.0.0.0 // 这样的话除了本地开发可预览,和开发机在同一个局域网访问 // 该计算机ip和端口一样可以访问,灵感来自react的控制台。 exports.getNetworkIp = () => { const os = require('os'); let needHost = ''; // 打开的host try { // 获得网络接口列表 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') // 设置进度条文字 const spinner = ora('正在打包...') // 开启进度条 spinner.start()rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { if (err) throw err webpack(webpackConfig, (err, stats) => { // webpack执行打包操作之后关闭进度条 spinner.stop() if (err) throw err process.stdout.write(stats.toString({ // 开启控制台颜色输出 colors: true, // 是否显示打包了源码文件 modules: false, // 如果你是typescript,开启此项则会导致报错 children: false, // 后面这两个个人感觉和开启了modules没太大区别,只是显示更加简略一些 // 是否显示生成的文件块 chunks: false, // 显示打包的代码块来源 chunkModules: false }) + '\n\n') // 当打包失败时 if (stats.hasErrors()) { console.log(chalk.red('打包失败\n')) process.exit(1) } // 当打包成功时 console.log(chalk.cyan(' 打包成功\n')) }) })

  • 那么大家看明白了吗?以上代码很多都是来自vue2.x脚手架,因为我觉得这些东西比脚手架3.0的控制台更加直观明了,但是我还是非常非常的喜欢V3的ui功能,相比控制台更加的美观,但是却不够方便,现在我们还差两样东西,在配置文件中我们配置了babel和postcss看过前两个文章的小伙伴一定清楚,这两个小家伙同样也需要配置信息,那么废话不多说,上代码!
// .babelrc { "presets": [ ["@babel/preset-env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } }] ], "plugins": ["@babel/transform-runtime"] }

此处你们会发现和上两个文章均不同,这里要注意啦!(敲黑板)在这里我直接升级了babel的版本使用的是版本7,那么以前的那一套就全部被废弃,现在的env就需要使用上面的配置而无需 babel-preset-stage-x 这个插件了,而 runtime 则也是换成了对应的版本。
// .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的配置就告一段落了,稍后我会放上我这个示例的最后完成文件,当然是针对vue的,因为最近一直也在学习vue啦~~~ 如果文档有落后的地方我会及时更新的。嘻嘻,那么我们webpack5.0见~ 附上工程: 提取码:v50a

    推荐阅读