找回密码
 注册
首页 ≡≡网络技术≡≡ WEB前端 Webpack构建多页应用Mpa(四):提取公共代码,实现单独打 ...

框架 Webpack构建多页应用Mpa(四):提取公共代码,实现单独打包

灰儿 2022-8-2 16:45:45
Webpack构建多页应用Mpa(四):提取公共js、css、html代码,实现图片、字体单独打包,拆分多环境配置文件

一、概述
多页面应用中,会有很多公共的重复使用的代码,例如:头部、底部、侧边栏这类公共的html代码,以及对应的css样式和js交互代码,这节教程将会给出方法。

二、开发
1、分离 业务模块代码 和 公共代码
截止到目前,整个项目的文件结构如下图:
13.png

我们对src下的文件结构调整如下:
14.png

2.修改webpack.config.js文件
将自动化方法中,检索目录由src改为src/page。
  1. function getMpa() {
  2.     const entry = {},
  3.         htmlwebpackplugins = [];
  4.     const entryfiles = glob.sync(path.resolve(__dirname, "./src/page/*/index.js"));
  5.     entryfiles.forEach(function (item) {
  6.         const folder = item.match(/\/src\/page\/(\w+)\/index\.js$/)[1];

  7.         entry[folder] = `./page/${folder}/index.js`;

  8.         htmlwebpackplugins.push(new HtmlWebpackPlugin({
  9.             title: `${folder}页面`,
  10.             filename: `./${folder}/index.html`,
  11.             template: `./page/${folder}/index.html`,
  12.             chunks: [folder],//以数组的形式指定由html-webpack-plugin负责加载的chunk文件(打包后生成的js文件),不指定的话就会加载所有的chunk。
  13.             inject: "body",//指示把加载js文件用的<script>插入到哪里,默认是插到<body>的末端,如果设置为'head',则把<script>插入到<head>里。
  14.             minify: true,//生成压缩后的HTML代码。
  15.         }));
  16.     });
  17.     return { entry, htmlwebpackplugins };
  18. }
复制代码

2、支持公共js、css代码
新建如下文件,
15.png
1.src/static/css/common.css
  1. /* common.css */
  2. body {
  3.     border: 2px solid red;
  4. }
复制代码

2.src/static/js/util.js
  1. // util.js
  2. const Util = {
  3.     getNow: function () {
  4.         console.log("util.js:公共方法getNow()");
  5.     }
  6. };

  7. module.exports = Util;
复制代码


3.src/static/js/launch.js
  1. // launch.js
  2. const run = {
  3.     init: function () {
  4.         console.log("launch.js:启动时自动运行");
  5.     }
  6. };
  7. $(function () {
  8.     run.init();
  9. })
复制代码

4.修改webpack.config.js文件
几处重要修改:

新增resolve配置项,配置公共文件别名;
  1. resolve: {
  2.        alias: {
  3.            '@': path.resolve(__dirname, ''),           //将@定位到项目根目录,require、import引入外部文件时注意
  4.            'jqueryMin': path.resolve(__dirname, 'src/static/js/jquery.min.js'),
  5.            'utilJs': path.resolve(__dirname, 'src/static/js/util.js'),
  6.        }
  7.    },
复制代码

新增ProvidePlugin插件,自动化引入jquery、com;
  1.     plugins: [
  2.         new webpack.ProvidePlugin({//代码中如果引用,则自动导入对应文件;未引入则不导入;
  3.             $: "jqueryMin",
  4.             'util': 'utilJs'                //调用方法:util.funcname(param);
  5.         })
  6.     ],
复制代码

修改getMpa方法,给entry入口添加公共css、js
其实即使在将entry各入口的值改成包含公共文件路径的数组;这样最终打包的时候,每个chunk就会包含公共代码;
  1. function getMpa(param) {
  2.     const entry = {},
  3.         htmlwebpackplugins = [];
  4.     const entryfiles = glob.sync(path.resolve(__dirname, "./src/page/*/index.js"));
  5.     entryfiles.forEach(function (item) {
  6.         const folder = item.match(/\/src\/page\/(\w+)\/index\.js$/)[1];

  7.         entry[folder] = (param && param.mustIncludeFile) ? param.mustIncludeFile.slice() : [];
  8.         entry[folder].push(`./page/${folder}/index.js`);

  9.         htmlwebpackplugins.push(new HtmlWebpackPlugin({
  10.             title: `${folder}页面`,
  11.             filename: `./${folder}/index.html`,
  12.             template: `./page/${folder}/index.html`,
  13.             chunks: [folder],//以数组的形式指定由html-webpack-plugin负责加载的chunk文件(打包后生成的js文件),不指定的话就会加载所有的chunk。
  14.             inject: "body",//指示把加载js文件用的<script>插入到哪里,默认是插到<body>的末端,如果设置为'head',则把<script>插入到<head>里。
  15.             minify: true,//生成压缩后的HTML代码。
  16.         }));
  17.     });
  18.     return { entry, htmlwebpackplugins };
  19. }

  20. //@表示src目录
  21. const { entry, htmlwebpackplugins } = getMpa({
  22.     mustIncludeFile: ["/static/js/launch.js", "/static/css/common.css"]
  23. });
复制代码

到这里就完成了,执行打包后,打开A页面时,预测会有以下效果:

1.控制台输出:“A页面”
2.控制台输出:“util.js:公共方法getNow()”
3.控制台输出:“launch.js:启动时自动运行”
4.common.css作用域页面,body会出现边框
16.png

3、自动识别html、css中引入的图片、字体文件
写html时,会用<img src="***">、style="background:***"引入静态资源;
写css时,会用background、font-face引入图片和字体资源
写js时,同样可以引入图片和字体外部资源
我们可以使用html-loader、url-loader 这两个loader来识别和配置图片和字体的打包工作;

安装loader
  1. npm i -D html-loader@0.5.5 url-loader@4.1.1

  2. //file-loader是url-loader的穷人版,file-loader中支持的功能,url-loader都原样支持;
  3. //url-loader基于file-loader开发,所以file-loader必须安装;
  4. //未解之谜:url-loader为什么不直接指定安装file-loader;
  5. npm i -D file-loade
复制代码

只装url-loader,不装file-loader时,控制台给出报错提示:
17.png

添加图片和字体文件:
18.png

在src/a/index.html加入图片:
  1. <body>
  2.     A页面
  3.     <img src="/image/beauty.jpg" alt="" width="100">
  4. </body>
复制代码

在src/a/index.css加入字体:
  1. @font-face {
  2.     font-family: alibaba;
  3.     src: url("/static/font/Alibaba-PuHuiTi-Light.ttf");
  4. }

  5. body{
  6.     font-family: alibaba;
  7.     background-color: white;
  8. }

复制代码

修改webpack.config.js文件
上面安装了url-loader、html-loader两个loader,前者用于识别图片和字体,并指定导出到哪个目录下;后者用于解释html代码,并提取出静态资源的路径;

添加配置代码:
  1. module.exports = {
  2.         context: path.resolve(__dirname, 'src'),
  3.         ...
  4.         module: {
  5.         rules: [
  6.             {
  7.                 test: /\.html$/,
  8.                 use: [
  9.                     {
  10.                         loader: "html-loader",
  11.                         options: {
  12.                             root: path.resolve(__dirname, 'src/static'),
  13.                             attrs: [":src"]
  14.                         }
  15.                     }
  16.                 ]
  17.             },
  18.             {
  19.                 test: /\.(png|gif|jpe?g)$/i,
  20.                 use: [
  21.                     {
  22.                         loader: 'url-loader',
  23.                         options: {
  24.                             name: '[name].[ext]',
  25.                             outputPath: "/static/image",
  26.                             publicPath: '/static/image',
  27.                             esModule: false,
  28.                             limit: 1e3,
  29.                         },
  30.                     }
  31.                 ]
  32.             },
  33.             {
  34.                 test: /\.(ttf|eot|woff2?|svg)$/i,
  35.                 use: [
  36.                     {
  37.                         loader: "url-loader",
  38.                         options: {
  39.                             name: "[name].[ext]",
  40.                             outputPath: "/static/font",
  41.                             publicPath: "/static/font",
  42.                             esModule: false,
  43.                             limit: 1e3
  44.                         }
  45.                     }
  46.                 ]
  47.             }
  48.         ]
  49.     },
  50. }
复制代码

html-loader规则中的options.root用于设置默认的上下文路径,如果不设置,系统默认以上一层基础配置项中的context为准,用于配置img.src属性中的路径。
url-loader规则中,options.outputPath用于设置文件输出到哪个目录,这里是设置输出到/dist/static/image和/dist/static/font;options.publicPaht用于替换代码中原来的路径,例如:
老代码:
  1. @font-face {
  2.     font-family: alibaba;
  3.     src: url("../../static/font/Alibaba-PuHuiTi-Light.ttf");
  4. }
复制代码

新代码:
  1. @font-face {
  2.     font-family: alibaba;
  3.     src: url(/static/font/Alibaba-PuHuiTi-Light.ttf);
  4. }
复制代码

最后打包后,图片和字体都会被放到src/static目录下,文件如图:
19.png

4、取消使用html-loader识别html中图片(啪啪打脸)
上面 3.自动识别html、css中引入的图片、字体文件 刚说用html-loader来做图片img标签路径提取,但是发现会对html代码做字符串化,导致ejs标签失效;

html-webpack-plugin默认支持ejs模板代码,在ejs中支持<%= htmlWebpackPlugin.options.title %>,可以直接获取配置项中的title属性值,但是在html-loader之后,对模板进行了字符串化,导致<%= *** %>不被解释。虽然html-loader有ignoreCustomFragments属性,可以跳过指定的tag标签,但如果我们就使用ejs写了整个html模板,总不能跳过全部代码吧。
所以弃用html-loader,html中图片改用·require·方式引入,例如:

<img src="<%= require('/static/image/beauty.jpg')  %> " alt="" width="100">
1
我们可以在webpack.config.js配置文件中,新增@img、@font这类别名,来简化路径;

//webpack.config.js代码
        ...
        resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src'),         
            '@img': path.resolve(__dirname, 'src/static/image'),
            '@font': path.resolve(__dirname, 'src/static/font'),
            'jqueryMin': path.resolve(__dirname, 'src/static/js/jquery.min.js'),
            'utilJs': path.resolve(__dirname, 'src/static/js/util.js'),
        }
    },

//html代码
<img src="<%= require('@img/beauty.jpg')  %> " alt="" width="100">

//css代码
@font-face {
    font-family: alibaba;
    src: url("@font/Alibaba-PuHuiTi-Light.ttf");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5、提取公共html代码,引入模板的概念
先上一张改造完成后的文件结构:

引入layout布局模板,主要借助htmlWebpackPlugin插件中的template属性。想法就是动态生成返回各个页面的字符串,填充到template属性。

新增layout目录存放模板代码,可以放头部、尾部、侧边栏、快捷方式等等,根据需要自由创建;layout/main.js是模板入口,用于渲染模板,返回模板字符串型代码,填充到template属性;
page目录下每个页面都添加一个main.js入口文件。
文件关系如下:

webpack.config.js通过htmlWebpackPlugin插件指定各页面入口文件,a/main.js、b/main.js等;
a.main通过layout/main.js中render方法,返回模板字符串代码;
layout/main.js代码:

const layout = require('./layout.html');

module.exports = {
    render(content, data) {
        return layout({
            content: content(data),
            header: require('./header.html')(data),
            footer: require('./footer.html')(data)
        });
    },
};
1
2
3
4
5
6
7
8
9
10
11
layout/layout.html代码:

<%= header %>
<%= content %>
<%= footer %>
1
2
3
layout/header.html代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>
        <%= pageTitle  %>
    </title>
</head>
<body>
    <div>头部</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
layout/footer.html代码:

        <div>底部</div>
</body>
</html>
1
2
3
page/A/main.js代码:

const content = require('./index.html');
const layout = require('layout');
const pageTitle = 'A页面';

module.exports = layout.render(content, { pageTitle });
1
2
3
4
5
webpack.config.js代码:
配置文件需要设置.html文件的loader,记得安装:npm i -D ejs-loader

module:{
        rules:{
                {
                test: /\.html$/,
                use: [{
                    loader: "ejs-loader",
                    options: {
                        esModule: false
                    }
                }]
            },
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
page/A/index.html、page/A/index.js、page/A/index.css文件代码不变,和原来一样。

到这里就完成整体实现。打包后,打开A页面如下:

到这里就基本实现了用模板方式,提取公共html代码的功能;

参考:构建一个简单的模板布局系统

6、设置webpack.config.js多环境配置文件
package.json新增命令行设置
  "scripts": {
    "dev": "npx webpack --config webpack.dev.js",        //开发环境
    "prod": "npx webpack --config webpack.prod.js"        //生产环境
  },
1
2
3
4
新建webpack.dev.js、webpack.prod.js
这里需要先安装webpack-merge,用于代码合并,npm i -D webpack-merge。
webpack.dev.js代码

const merge = require("webpack-merge");
const baseConfig = require("./webpack.base.js");
module.exports = merge(baseConfig, {
    mode: "development"
});
1
2
3
4
5
webpack.prod.js代码

const merge = require("webpack-merge");
const baseConfig = require("./webpack.base.js");

module.exports = merge(baseConfig, {
    mode: "production"
});
1
2
3
4
5
6
webpack.config.js代码

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const glob = require("glob");
const webpack = require("webpack");

function getMpa(param) {
    const entry = {},
        htmlwebpackplugins = [];
    const entryfiles = glob.sync(path.resolve(__dirname, "./src/page/*/index.js"));
    entryfiles.forEach(function (item) {
        const folder = item.match(/\/src\/page\/(\w+)\/index\.js$/)[1];

        entry[folder] = (param && param.mustIncludeFile) ? param.mustIncludeFile.slice() : [];
        entry[folder].push(`./page/${folder}/index.js`);

        htmlwebpackplugins.push(new HtmlWebpackPlugin({
            title: `${folder}页面`,
            filename: `./${folder}/index.html`,
            template: `./page/${folder}/main.js`,
            chunks: [folder],//以数组的形式指定由html-webpack-plugin负责加载的chunk文件(打包后生成的js文件),不指定的话就会加载所有的chunk。
            inject: "body",//指示把加载js文件用的<script>插入到哪里,默认是插到<body>的末端,如果设置为'head',则把<script>插入到<head>里。
            minify: true,//生成压缩后的HTML代码。
        }));
    });
    return { entry, htmlwebpackplugins };
}

const { entry, htmlwebpackplugins } = getMpa({
    mustIncludeFile: ["/static/js/launch.js", "/static/css/common.css"]
});

module.exports = {
    context: path.resolve(__dirname, 'src'),
    entry,
    output: {
        filename: "[name]/index.js",
        path: path.resolve(__dirname, "dist"),
        chunkFilename: "[name]/index.js"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, "css-loader"]
            },
            {
                test: /\.(png|gif|jpe?g)$/i,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            name: '[name].[ext]',
                            outputPath: "/static/image",
                            publicPath: '/static/image',
                            esModule: false,
                            limit: 1e3,
                        },
                    }
                ]
            },
            {
                test: /\.(ttf|eot|woff2?|svg)$/i,
                use: [
                    {
                        loader: "url-loader",
                        options: {
                            name: "[name].[ext]",
                            outputPath: "/static/font",
                            publicPath: "/static/font",
                            esModule: false,
                            limit: 1e3
                        }
                    }
                ]
            },
            {
                test: /\.html$/,
                use: [{
                    loader: "ejs-loader",
                    options: {
                        esModule: false
                    }
                }]
            },
        ]
    },
    plugins: [
        ...htmlwebpackplugins,
        new CleanWebpackPlugin(),
        new webpack.ProvidePlugin({//代码中如果引用,则自动导入对应文件;未引入则不导入;
            $: "jqueryMin",
            'util': 'utilJs'
        }),
        new MiniCssExtractPlugin({
            filename: "[name]/index.css",
            chunkFilename: "[name]/index.css"
        })
    ],
    resolve: {
        // extensions: ['.js', '.vue', '.json'],
        alias: {
            '@': path.resolve(__dirname, 'src'),           //将@定位到项目根目录,require、import引入外部文件时注意
            '@img': path.resolve(__dirname, 'src/static/image'),
            '@font': path.resolve(__dirname, 'src/static/font'),
            'jqueryMin': path.resolve(__dirname, 'src/static/js/jquery.min.js'),
            "layout": path.resolve(__dirname, 'src/layout/main.js'),
            'utilJs': path.resolve(__dirname, 'src/static/js/util.js'),
            // 'commonCss': path.resolve(__dirname, 'src/static/css/common.css'),
        }
    },
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
执行npm run dev开发模式打包,npm run prod生产模式打包。
两种模式的配置文件,可以按照es语法、文件压缩模式、各个包浏览器支持程度等进行不同的配置,具体参考webpack官网优化项目:Optimization

三、结束
开发过程有一些功能未完成和遇到的问题,记录在这里,后面继续完善:

ejs本身支持include引入功能,但是实测无效,后面要排查实现include功能;
css-loader开启modules:true后,css实现了class哈希,但是html中的classname还是原来名字;
尝试将多页面Mpa项目中的某一个页面做成SPA;
babel-loader、postcss-loader对js和css进行优化;
————————————————
版权声明:本文为CSDN博主「IT飞牛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bobo789456123/article/details/118095614


您需要登录后才可以回帖 登录 | 注册
学习中心
站长自定义文字内容,利用碎片时间,随时随地获取优质内容。
Q设计语言 了解更多
Q Design 提供商家设计所需的指导与资源,帮商家快速完成产品设计、降低生产成本。
学习中心
站长自定义文字内容,利用碎片时间,随时随地获取优质内容。
Q设计语言 了解更多
Q Design 提供商家设计所需的指导与资源,帮商家快速完成产品设计、降低生产成本。