webpack多页面入口生产项目开发配置

栏目: 编程工具 · 发布时间: 6年前

内容简介:这不是一个纯粹的学习帖子,最开始为了生产项目考虑的。公司有个新的、小的活动项目。以此为假想,所以我希望学习一些新的技术应用在上面;这个新的项目是作为旧项目的一个子系统存在的,所以又必须在一定程度上保持一致。而这个旧项目的原有使用构建工具fis的版本比较老旧,不敢升级,怕出什么幺蛾子,所以又不能动他。在网上学习了众多攻略之后自己尝试搭建了一下,解决了一些问题,也留下了一下疑惑。

这不是一个纯粹的学习帖子,最开始为了生产项目考虑的。公司有个新的、小的活动项目。以此为假想,所以我希望学习一些新的技术应用在上面;这个新的项目是作为旧项目的一个子系统存在的,所以又必须在一定程度上保持一致。

而这个旧项目的原有使用构建工具fis的版本比较老旧,不敢升级,怕出什么幺蛾子,所以又不能动他。

在网上学习了众多攻略之后自己尝试搭建了一下,解决了一些问题,也留下了一下疑惑。

项目资源路径

github: github.com/johnshere/a…

一、环境配置

node: v10.6.0(v6.12.3)、yarn: 1.7.0、webpack: 4.16.1

系统:windows10

yarn是类似npm但是效率更高的包管理工具,命令互换参考 yarnpkg.com/zh-Hans/doc…

可以使用npm安装(这里让我想到IE存在的意义,-_-)

npm install -g yarn
复制代码

也可以去官网下载客户端

二、目录结构

如图:

webpack多页面入口生产项目开发配置

src:工程源代码; release:工程发布; webpack.config:webpack配置文件;

发布之后的HTML保持与src中的路径一致;这样代码中使用相对路径访问页面就不会出现结构错乱的问题。

webpack多页面入口生产项目开发配置

src目录下有三个文:entry.json/index.html/index.js;

这是目录索引页面,因为是多页面入口,webpack-dev-server模式打开的时候用于快速进入自己想要的页面(下面会说)

三、两个构建环境切换(与webpack无关)

公司原有的fis是最初版本的,一直没有人做更新维护,现在已经落后现在的技术版本多年,但是又必须使用。

webpack4不可能在和他进行兼容,所以我安装了两个不同版本的node,v10.6.0、v6.12.3;使用的时候切换

webpack多页面入口生产项目开发配置

当然实际的环境变量配置了一个,然后我写了一个脚本,执行命令(changeNodeName)后切换文件夹名称,把这个脚本放在node个目录下,如下图:

webpack多页面入口生产项目开发配置

脚本很简单,就是判断文件夹名称、改变名称;改变后的名称保持和环境变量里面的名字一直就行。这样做的问题也很大,就是没有办法同时编辑两个工程。

set dir=D:
set name1=node612
set name2=node106
set name=node
if exist %dir%\%name1% (
	echo "node612 ==> node"
	ren %dir%\%name% %name2%
	ren %dir%\%name1% %name%
)else (
	echo "node106 ==> node"
	ren %dir%\%name% %name1%
	ren %dir%\%name2% %name%
) 
pause
复制代码

这样切换了node,实际上就是切换整个开发环境,毕竟这两个构建 工具 都是依赖于node的。

切换时在cmd或者powershell里执行:

changeNodeName

四、webpack配置

这个应该是重中之重了,在写配置之前我首先确定了自己想解决的一些问题

  1. 发布后保证目录结构不变
  2. 分割公共文件,如样式、图片;达到缓存目的
  3. 分割的大文件不能过大(未解决)、不能让用户频繁加载
  4. 保证文件之间缓存良好互不干扰
  5. 转义语法

1、webpack.entry.util.js

const path = require("path");
const Glob = require("glob");
const fs = require("fs");

let obj = {
    /**
     * 根据目录获取入口
     * @param  {[type]} globPath [description]
     * @return {[type]}          [description]
     */
    getEntryJs: function (globPath) {
        globPath = path.resolve(__dirname, globPath);
        let entries = {};
        Glob.sync(globPath).forEach(function (entry) {
            let basename = path.basename(entry, path.extname(entry)),
                pathname = path.dirname(entry),
                paths = pathname.split('/'),
                fileDir = paths.splice(paths.indexOf("src") + 1).join('/');

            //仅处理page路径下的js
            if (pathname.indexOf("page") > -1) {// && fileDir && fileDir.indexOf(("page") === 0)) {
                entries[(fileDir ? fileDir + '/' : fileDir) + basename] = pathname + '/' + basename;
            }

        });
        //目录页保留
        entries["index"] = path.resolve(__dirname,"../src/index").split("\\").join("/");
        console.log("---------------------------------------------\nentries:");
        console.log(entries);
        console.log("----------------------------------------------");
        return entries;
    },
    /**
     * 根据目录获取 Html 入口
     * @param  {[type]} globPath [description]
     * @return {[type]}          [description]
     */
    getEntryHtml: function (globPath) {
        globPath = path.resolve(__dirname, globPath);
        let entries = [];
        Glob.sync(globPath).forEach(function (entry) {
            let basename = path.basename(entry, path.extname(entry)),
                pathname = path.dirname(entry),
                paths = pathname.split('/'),
                // @see https://github.com/kangax/html-minifier#options-quick-reference
                minifyConfig = process.env.NODE_ENV === "production" ? {
                    removeComments: true,
                    // collapseWhitespace: true,
                    minifyCSS: true,
                    minifyJS: true
                } : "";
            //只处理page目录下的HTML
            //保留目录页
            if (entry.indexOf("page") > -1 ) {
                let chunkName = paths.splice(paths.indexOf("src") + 1).join('/') + "/" + basename;

                entries.push({
                    filename: chunkName + ".html",
                    template: entry,
                    chunks: ['public/vendor', chunkName],
                    minify: minifyConfig
                });
            }
        });
        //保留目录页
        entries.push({
            filename: "index.html",
            template: path.resolve(__dirname,"../src/index.html").split("\\").join("/"),
            chunks: ['public/vendor',"index"]
        });
        //保存entry的json文件
        this.entry2JsonFile(entries);

        return entries;
    },
    /**
     * 生成entry对应的json文件
     * @param entries
     */
    entry2JsonFile: function (entries) {
        console.log(entries);
        let json = {};
        if (entries) {
            entries.forEach(v => {
                json[v.filename] = v.filename;
            });
        }
        console.log(json);

        //同步写入文件
        let fd = fs.openSync(path.resolve(__dirname, "../src/entry.json"), "w");
        fs.writeSync(fd, JSON.stringify(json), 0, "utf-8");
        fs.closeSync(fd);
    }
};
// obj.getEntry("../src/page/**/*.js");
// obj.getEntryHtml('../src/page/**/index.html');

module.exports = obj;
复制代码

这个地方的entry识别参考了:

github地址: github.com/givebest/we…

这个entry工具主要是为了识别js和HTML;我在原有的逻辑上进行了修改,符合了我的要求,即只识别page目录下的entry。

同时,我添加了一个方法,即将所有的HTML路径写入到一个json文件中保存起来(后面dev-server模式用到)。前两个方法里也为入口目录页做了特殊处理 这个工具中对chunk的key值做了特殊处理,可以看出,切割出了从src之后的路径作为key值,因为webpack的name是支持路径的,这样就达到问题1的效果。

2、webpack.base.conf.js

const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const entryUtil = require("./webpack.entry.util");
let entryJs = entryUtil.getEntryJs('../src/page/**/index.js');

let conf = {
    entry: entryJs,//js打包入口识别
    output: {
        path: path.resolve(__dirname, "../release"),
        filename: "[name].[chunkHash].js",
        // publicPath: "../../public"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                // loader: ExtractTextPlugin.extract({
                //     fallback: 'style-loader',
                //     use: 'css-loader'
                // })
                use:[MiniCssExtractPlugin.loader,'css-loader']//'style-loader',
            },
            {
                test: /\.html$/,
                loader: 'html-withimg-loader'
            },
            {test: require.resolve("jquery"), loader: "expose-loader?$!expose-loader?jQuery"}
        ]
    },
    plugins: [
        // new HtmlWebpackPlugin({
        //     filename: "index.html",
        //     template: "src/page/index.html",
        //     chunks: ["main", "vender"]
        // }),
        // new ExtractTextPlugin("./[name].[chunkHash].css")
        new CleanWebpackPlugin(["release"],{
            root: path.resolve(__dirname, ".."),
            verbose: true,
            dry: false
        }),
        new MiniCssExtractPlugin({
            filename: "[name].[contenthash:7].css",
            chunkFilename: "[name].[contenthash].css"
        })
    ],
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    name: "public/vendor",
                    chunks: "all",
                    minChunks: 2
                }
            }
        }
    },
    resolve: {
        extensions: [".js", ".jsx"],
        alias: {
            layer: path.resolve(__dirname, "../src/public/js/layer/mobile/layer.js"),
            "layer.css": path.resolve(__dirname, "../src/public/js/layer/mobile/need/layer.css")
        }
    }
};
//HTML入口
let entryHtml = entryUtil.getEntryHtml('../src/page/**/index.html');

entryHtml.forEach(function (v) {
    conf.plugins.push(new HtmlWebpackPlugin(v));
});
module.exports = conf;
复制代码

这里就需要给解释了,开始学习webpack,然后网上不断找各种帖子,学习、修改、测试最终成了这些配置文件,有些改动时间长了我自己都忘记(-_-)!。

2.1 获取entry、HtmlWebpackPlugin

使用工具获取指定的HTML和js,这里我做一个限制,只取index名称的,这是因为公司很多模板文件都是用html后缀。

webpack的入口是只识别js的,这里就需要用到HtmlWebpackPlugin,没生成一个HTML与js的对应关系就要new一个HtmlWebpackPlugin。所以上面entryHtml是push进去的,还有就是entryHtml中做了生产环境的判断。

2.2 分割css文件

现在使用的是MiniCssExtractPlugin,但是从注释开出来我最开始使用的是ExtractTextPlugin(我也是从注释看到才想起来的,哈哈哈哈)。

先说ExtractTextPlugin,这个要在webpack4上面用正常安装是不行的,现在必须指定版本@next,否则不能兼容webpack4。如下:

yarn add ExtractTextPlugin@next
复制代码

配置好了之后,我用了一段时间,最后在思考上面第四个问题的时候,把这个替换掉了,ExtractTextPlugin好像不能使用contenthash。

我们公司是做bss系统的,业务复杂,而且更换业务逻辑的频率很快,所以index.js修改比较多,但是样式和图片其实改动不多,不能因为改了一个if else,就需要用户更新css和图片吧。所以换成MiniCssExtractPlugin现在的样子。

然后关于MiniCssExtractPlugin的配置

filename是配置每个chunk对应分割出css文件的配置

chunkfilename是配置分离出的公共css文件的配置

webpack多页面入口生产项目开发配置

2.3 加载jquery

jquery没有实现模块化,在loader里面做了特殊处理;这样之后在每个js里面就可以使用require或者import引入jquery

但是实际上,这个只能达到引入效果,$还是全局对象。

2.4 HTML中的图片路径

我在有些前辈的帖子中看到是需要在HTML标签中加一下引用判断、loader标识;这样很不友好;这里使用了一个loader:html-withimg-loader,用这个loader,就不用管了,他自己处理HTML中出现的图片链接。

2.5 清理

清理已经存在的文件,如果不清理每次发布都会有残余文件,虽然没有什么影响,但是不能忍。 CleanWebpackPlugin可以指定清理的正则配置,如:

new CleanWebpackPlugin(["release"],{
            root: path.resolve(__dirname, ".."),
            verbose: true,
            dry: false
        }),
复制代码
new CleanWebpackPlugin(["release/*.js","release/**/*.*"],{
            root: path.resolve(__dirname, ".."),
            verbose: true,
            dry: false
        }),
复制代码

3、webpack.devServer.conf.js

开发环境

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

const webpack = require("webpack");
const merge = require('webpack-merge');

const base = require('./webpack.base.conf');

// process.env.NODE_ENV = "development";

module.exports = merge(base, {
    mode: "development",
    devtool: "eval-source-map",
    output: {
        path: path.resolve(__dirname, "../release"),//"../release_dev"),
        filename: "[name].[hash].js",
    },
    module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                // loader: 'url-loader?limit=8192&name=./public/images/[name].[hash].[ext]'
                loader: {
                    loader: 'url-loader',
                    options: { // 这里的options选项参数可以定义多大的图片转换为base64
                        name: '[name].[hash].[ext]',
                        // limit: 8192, // 表示小于50kb的图片转为base64,大于50kb的是路径
                        // outputPath: '/public/images' //定义输出的图片文件夹
                    }
                }
            }
        ]
    },
    plugins:[
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        port: 8080,
        contentBase: path.resolve(__dirname, "../release"), //本地服务器所加载的页面所在的目录
        historyApiFallback: true, //不跳转
        inline: true, //实时刷新
        hot: true, // 开启热更新,
        //服务器代理配置项
        proxy: {
            '/o2o/*':{
                target: 'https://www.baidu.com',
                secure: true,
                changeOrigin: true
            }
        }
    }
});
复制代码

这个在base的基础上做了些许调整,主要是为了使用webpack-dev-server;这个配置文件是为它存在的。

3.1 output hash

这里的hash有chunkhash改成hash,原因是使用HotModuleReplacementPlugin之后不能使用chunkhash和contenthash。

看到有些地方说把“hot:true”去掉就行了,但是我自己实际测试不行,只是去掉hot还是会报错;所以我索性给改成hash了,反正是本机调试,影响不大。

3.2 devServer

这个功能很强大,对开发人员来说是非常友好的。

安装webpack-dev-server

yarn add webpack-dev-server
复制代码

这个代理proxy功能还是非常强大的,将后台服务请求指向我们的测试环境或者本地。我们原有的fis是包装了一层nginx,每次还要单打开,单独配置nginx。这里集成这个功能,很好。本地开发减少依赖,也便于调试。

3.4 入口(entry)目录页

前面在entry工具中将所有的entry写入到一个json文件中了。在这个地方就用到了,我们项目本质上根本不是spa,使用webpack还是比较牵强的。

当启动了webpack-dev-server之后它会默认打开根目录下的index.html。其实我们项目的页面很多,不论默认打开哪个都不方便开发,我干脆把这个index.html做成了一个目录页面。将entry.json中所有的路径全显示,点击之后进入各个页面。

// const $ = require("jquery");
import $ from "jquery";
const entryJson = require("./entry.json");
console.log(1122333,entryJson);
$(() => {
    $("html").css("font-size","16px");
    for (let k in entryJson){
        $("body").append("<a style='margin: 1rem;padding-left:3rem;font-size: 2rem;line-height: 2rem;display: block' href='"+entryJson[k]+"'>"+entryJson[k]+"</a></br>");
    }
});
复制代码
webpack多页面入口生产项目开发配置

4、webpack.pro.conf.js

生产环境

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

const merge = require('webpack-merge');
const base = require('./webpack.base.conf');

module.exports = merge(base, {
    mode: "production",
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    name: "public/vendor",
                    chunks: "all",
                    minChunks: 2
                }
            }
        }
    },
    module: {
        rules: [
             {
                 test:/\.js$/,
                 exclude: /node_modules/,
                 loader: "babel-loader"
             },
            {
                test: /\.(png|jpg|gif)$/,
                // loader: 'url-loader?limit=8192&name=./public/images/[name].[hash].[ext]'
                loader: {
                    loader: 'url-loader',
                    options: { // 这里的options选项参数可以定义多大的图片转换为base64
                        name: '[name].[hash].[ext]',
                        limit: 8192, // 表示小于的图片转为base64,大于的是路径
                        outputPath: 'public/images' //定义输出的图片文件夹
                    }
                }
            }
        ]
    }
});
复制代码

这个生产的配置也是在前面的base基础上调整的。

4.1 发布目录调整

这个小的工程是作为一个子工程存在于旧项目,所以url不是直接访问的,需要加上“工程名”的一级路径。url-loader的outputPath、所有chunkname都需要多加一段“activity”,具体需要自己调试。

这个地方有个需要注意,最开始尝试的时候,我想只要只要改output就行了;但是测试之后才发现不行。原因很简单,这个图片src是给浏览器用的,是统一资源定位符。仅仅调整output的path是不会在定位符上加“activity”的,那仅仅是改变了发布后文件保存的路径。例如:

optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    name: "activity/public/vendor",
                    chunks: "all",
                    minChunks: 2
                }
            }
        }
    },
复制代码
{
                test: /\.(png|jpg|gif)$/,
                // loader: 'url-loader?limit=8192&name=./public/images/[name].[hash].[ext]'
                loader: {
                    loader: 'url-loader',
                    options: { // 这里的options选项参数可以定义多大的图片转换为base64
                        name: '[name].[hash].[ext]',
                        limit: 8192, // 表示小于的图片转为base64,大于的是路径
                        outputPath: 'activity/public/images' //定义输出的图片文件夹
                    }
                }
            }
复制代码

4.2 图片分割

如代码中展示这里使用了url-loader,并且设定limit;当图片超过limit限制会单独生成文件,否则就是base64存储。

但是这里我遇到一个棘手问题,当图片单独存储时,options.name的hash值不能设置成contentHash或者chunkHash,并且也没有找到合适的解决办法,希望知道的朋友给我说一下。(虽然在一定程度上说不用hash值也行,但是我感觉这样不好)

4.3 babel编译

使用babel转义ECMAScript6的语法,使之兼容旧的浏览器。如代码中设置loader,然后在项目根目录创建新文件.babelrc,内容:

{
  "presets": ["env"]
}
复制代码

安装babel

yarn add babel-core babel-loader babel-preset-env
复制代码

4.4 mode NODE_env

这里在webpack配置文件中设置了mode:production,并且在启动脚本中也设置node的环境为production。删掉了devtool。 这里设置的环境配合entry工具中对环境的识别,会配置压缩设置。

package.json的scripts

如下:

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack --config ./webpack.config/webpack.dev.conf.js",
    "pro": "cross-env NODE_ENV=production webpack --config ./webpack.config/webpack.pro.conf.js --progress",
    "devServer": "webpack-dev-server --config ./webpack.config/webpack.devServer.conf.js --open --mode development",
    "watch": "webpack --config ./webpack.config/webpack.dev.conf.js --watch"
  }
}
复制代码

首先安装cross-env,用于设置node环境;在上面的脚本中可以看到cross-env的使用

yarn add cross-env
复制代码

上面设置两个webpack的配置文件,但是没有实际使用,其实使用的命令就是scripts中的内容。只不过这里可以是操作简化,但我们使用时只需要启动脚本,如下: 开发环境:

yarn run devServer
复制代码

生产环境:

yarn run pro
复制代码

run也是可以省略的。 webpack-dev-server模式下不会将实际发布的内容写入在硬盘上,如果我们需要自行查看内容,可以执行:

yarn run watch
复制代码

只不过这样做意义不大,因为我发现,你每次修改都会产生一些列文件,很快你就发现生成的是一堆垃圾,从中找东西费劲的很。

问题遗留

  1. 大图片大单分割出来后无法使用contenthash,我如何能让一个大图长久缓存呐
  2. 公共文件过大,仅我写的这个测试工程vender就已经一兆多,感觉不是很大,但是真实项目中就很可怕了。而且我们项目是移动端的,这样大文件下载的留白时间也很难受。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python Algorithms

Python Algorithms

Magnus Lie Hetland / Apress / 2010-11-24 / USD 49.99

Python Algorithms explains the Python approach to algorithm analysis and design. Written by Magnus Lie Hetland, author of Beginning Python, this book is sharply focused on classical algorithms, but it......一起来看看 《Python Algorithms》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具