基于业务本身考虑,针对企业官网性质、面向C端用户的PC网站。我们需要有良好的搜索体验,需要有良好的页面访问体验。对此,探讨webpack配置多页面开发工程的可能性。本篇文章,默认大家对于webpack已经具有一定的了解,可以完成一些基础的webpack配置操作。如有不清楚的,请[点击查看](https://www.webpackjs.com/)。
整体配置,我将其拆分为5个部分的配置。分别是:
- webpack.config.js : 所有配置信息的整合合并配置,和基础的webpack配置。
- config/plugins.js :webpack的所有插件配置信息
- config/proxy.js :将webpack中的代理信息单独拿出来进行配置管理
- config/rules.js:webpack的加载器配置模块独立配置管理
- router.js:这是新增自定义的路由配置,由于自定义路由地址和指定配置路由文件。
config/rules.js
本部分配置,主要是为了给工程提供更多的适配,如ts、less、scss、postcss等功能。
/* eslint-disable no-undef */ const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ); const rules = [ { test: /\.js$/, loader: 'eslint-loader', enforce: 'pre', include: [path.resolve( __dirname, 'src' )], // 指定检查的目录 options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine formatter: require( 'eslint-friendly-formatter' ), // 指定错误报告的格式规范 }, }, { test: /\.[tj]s?$/, exclude: /node_modules/, use: ['babel-loader'], }, { test: /\.tsx?$/, exclude: /node_modules/, use: [ { loader: 'ts-loader', options: { transpileOnly: true, }, }, ], }, { test: /\.ts$/, exclude: /node_modules/, use: ['ts-loader'] }, { test: /\.ejs$/, use: [ { loader: 'ejs-loader', options: { esModule: false, variable: 'data', }, }, ], }, { test: /\.(sa|sc|le|c)ss$/, use: [ { // 把js中import导入的样式文件,单独打包成一个css文件,结合html-webpack-plugin,以link的形式插入到html文件中。 loader: MiniCssExtractPlugin.loader, // options: { // publicPath: '../',//设置publicPath,解决css文件中background背景图片路径问题 // }, }, // 把js中import导入的样式文件打包到js文件中,运行js文件时,将样式自动插入到<style标签中,style-loader不能和mini-css-extract-plugin同时使用 // 'style-loader', 'css-loader', 'postcss-loader', 'less-loader', 'sass-loader', ], }, { test: /\.eot$|\.svg$|\.ttf$|\.woff$/, use: ['url-loader'], type: 'asset/resource', }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', generator: { filename: 'images/[name]_[hash]_[ext]' }, }, { test: /\.(csv|tsv)$/i, use: ['csv-loader'], }, { test: /\.xml$/i, use: ['xml-loader'], }, ] module.exports = rules;
config/plugins.js
这部分是用于完成webpack的插件配置工作的,我们这里提供了css合并分立插件、构建清空已有文件,最重要的是实现ejs模块化转html的工作。
/* eslint-disable no-undef */ const path = require('path'); // Node.js 处理文件路径的模块 const webpack = require('webpack'); const HTMLWebpackPlugin = require('html-webpack-plugin'); const glob = require('glob'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); // webpack 5.x 使用 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // minify extract js to css const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const router = require('./router'); const { routes } = router; // const autoprefixer = require('autoprefixer'); // webpack.config.js 通用插件配置 const getHtmlWebpackPlugins = () => { const mode = process.env.TEST_MODE || null; console.log('get plugin mode:', mode) if (mode && mode === 'template') { // 模板测试 return glob.sync(path.resolve(__dirname, '../test/templates') + '/**/*.{jsx,ejs}').map(item => { var prefixPath = path.resolve(__dirname, '..\/test\/templates'); var filePath = item.substring(prefixPath.length + 1, item.lastIndexOf('.')); console.log('加载模板路径:', filePath); return new HTMLWebpackPlugin({ template: item, filename: filePath + '.html', minify: { // 压缩 HTML 文件 removeComments: true, // 移除 HTML 中的注释 collapseWhitespace: true, // 删除空白符与换行符 minifyCSS: true // 压缩内联 css }, favicon: './config/icon.png', title: 'title', chunks: ['common', 'test/'+filePath], inject: true, }) }); } return routes.map(item => { console.log('loading template info:', JSON.stringify(item)); const filename = item.path.lastIndexOf('\/') === String(item.path).length - 1 ? item.path + 'index.html' : item.path + ".html"; const chunks = ['common']; if (item.main) { chunks.push(item.path.substring(1)); } console.log('get chunks:',chunks) return new HTMLWebpackPlugin({ template: path.resolve(__dirname, '../src/templates/' + item.template), filename:filename.substring(1), minify: { // 压缩 HTML 文件 removeComments: true, // 移除 HTML 中的注释 collapseWhitespace: true, // 删除空白符与换行符 minifyCSS: true // 压缩内联 css }, favicon: './config/icon.png', title: item.name, chunks, inject: true, }) }) } const plugins = [ new webpack.ProvidePlugin({ // 配置shim预置依赖 $: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery', Popper: ['popper.js', 'default'], // In case you imported plugins individually, you must also require them here: Util: "exports-loader?Util!bootstrap/js/dist/util", Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown", }), require('autoprefixer'), // autoprefixer可以自动在样式中添加浏览器厂商前缀,避免手动处理样式兼容问题 new MiniCssExtractPlugin({ // 类似 webpackOptions.output里面的配置 可以忽略 css 文件分离 filename: 'css/[name].[chunkhash:8].css', chunkFilename: '[id].css', }), new CssMinimizerPlugin(), // 配置css-minimizer-webpack-plugin new webpack.DefinePlugin({ 'process.env.build_lang': `"${process.env.build_lang}"`, }), // 如果不想在 watch 触发增量构建后删除 index.html 文件,可以在 CleanWebpackPlugin 中配置 cleanStaleWebpackAssets 选项 来实现 new CleanWebpackPlugin({ cleanStaleWebpackAssets: false }), ...getHtmlWebpackPlugins(), new webpack.HotModuleReplacementPlugin(), ] module.exports = plugins;
config/router.js
这里主要是路由的自定义配置,实现指定页面的指定js依赖的功能。
/* eslint-disable no-undef */ // 路由配置文件 const routes = [ { path: '/index', template: 'index.ejs', name: '首页', }, { path: '/page/1', template: 'page/1.ejs', name: '测试页面', main: '/pages/test.js', }, ] module.exports = { routes };
webpack.config.js
/* eslint-disable no-undef */ const path = require("path"); // Node.js 处理文件路径的模块 const glob = require("glob"); const plugins = require("./config/plugins"); // 配置插件 const rules = require("./config/rules"); // 加载器 const proxy = require("./config/proxy"); const router = require("./config/router"); const { routes } = router; /** * 入口文件 */ const entries = function () { const mode = process.env.TEST_MODE || null; let map = { common: ["@/app.js", "lodash"], }; if (process.env.NODE_ENV === "development") { map.hot = "webpack/hot/dev-server"; map.client = "webpack-dev-server/client/index.js?hot=true&live-reload=true"; } if (mode && mode === "template") { // 模板测试 // add test entries glob .sync(path.resolve(__dirname, "./test/templates") + "/**/*.{js,ts}") .forEach((item) => { let prefixPath = path.resolve(__dirname, "./test/templates"); let filePath = item.substring( prefixPath.length + 1, item.lastIndexOf("."), ); console.log("loading module", filePath); map["test/" + filePath] = item; }); } else { routes .filter((item) => item.main) .map((item) => { const main = path.resolve(__dirname, "./src" + item.main); console.log("loading entry file:", main); map[item.path.substring(1)] = main; }); } return map; }; module.exports = { // 入口文件 // name: language, // 语言名称 entry: entries(), watch: true, watchOptions: { poll: 1000, // 每秒询问多少次 aggregateTimeout: 500, //防抖 多少毫秒后再次触发 ignored: /node_modules/, //忽略时时监听 }, mode: process.env.NODE_ENV === "production" ? "none" : "development", output: { // 存放打包后的文件的位置 path: path.resolve(__dirname, "./dist"), // 打包后的文件名 filename: "[name].bundle.[contenthash:8].js", chunkFilename: "[name].js", // 代码拆分后的文件名 publicPath: "/", }, target: "web", cache: { type: "memory", }, module: { rules: rules }, plugins: [ ...plugins, // new I18nPlugin(rq), ], devServer: { devMiddleware: { index: true, mimeTypes: { "text/html": ["phtml"] }, publicPath: "/dist", serverSideRender: true, writeToDisk: true, }, static: "./dist", proxy, // publicPath: 'http://localhost:8080', // port: 9000, historyApiFallback: true, compress: true, hot: false, client: false, open: true, // when open is enabled, the dev server will open the browser. }, resolve: { extensions: [".ts", ".js", ".json"], alias: { "@": path.resolve(__dirname, "./src"), }, }, context: path.resolve(__dirname), };
webpack.config.js是webpack打包的配置整合,可通过如上配置实现简单的多页面配置性编译,新增路由可通过在config/router.js中添加页面路由配置即可。