首页 > 其他分享 >【webpack4系列】编写可维护的webpack构建配置(四)

【webpack4系列】编写可维护的webpack构建配置(四)

时间:2024-09-15 21:40:33浏览次数:11  
标签:npm webpack4 const require js webpack test 编写

构建配置包设计

构建配置管理的可选方案:

  • 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
  • 将构建配置设计成一个库,比如:xxx-webpack
  • 抽成一个工具进行管理,比如:create-vue-app
  • 将所有的配置放在一个文件,通过 --env 参数控制分支选择

通过多个配置文件管理不同环境的 webpack 配置

  • 基础配置:webpack.base.js
  • 开发环境:webpack.dev.js
  • 生产环境:webpack.prod.js
  • SSR环境:webpack.ssr.js
  • ……

抽离成一个 npm 包统一管理

  • 规范:Git commit日志、README、ESLint 规范、Semver 规范
  • 质量:冒烟测试、单元测试、测试覆盖率和 CI

通过 webpack-merge 组合配置

const merge = require("webpack-merge")
// 省略其他代码
module.exports = merge(baseConfig, devConfig);

功能模块设计和目录结构设计

功能模块设计

构建包设计:

  • 基础配置:webpack.base.js
    • 资源解析
      • 解析ES6
      • 解析vue
      • 解析react
      • 解析css
      • 解析less
      • 解析scss
      • 解析图片
      • 解析字体
    • 样式增强
      • CSS前缀补齐
      • CSS px转成rem
    • 目录清理
    • 多页面打包
    • 命令行信息显示优化
    • 错误捕获和处理
    • CSS提取成一个单独的文件
  • 开发配置:webpack.dev.js
    • 代码热更新
      • css热更新
      • js热更新
    • sourcemap
  • 生产配置:webpack.prod.js
    • 代码压缩
    • 文件指纹
    • Tree Shaking(webpack4自带)
    • Scope Hositing(webpack4自带)
    • 速度优化(基础包CDN等)
    • 体积优化(代码分割)
  • SSR 配置:webpack.ssr.js
    • output的libraryTarget设置
    • css解析ignore

目录结构设计

  • lib 放置源代码
  • test 放置测试代码

结构如下:

+ |- /test
+ |- /lib
+ |- webpack.dev.js
+ |- webpack.prod.js
+ |- webpack.ssr.js
+ |- webpack.base.js
+ |- README.md
+ |- CHANGELOG.md
+ |- .eslinrc.js
+ |- package.json
+ |- index.js`

使用ESLint规范构建脚本

使用 eslint-config-airbnb-base

eslint --fix 可以自动处理空格。

安装的插件:

npm i eslint@7 babel-eslint eslint-config-airbnb-base -D

我们在工程根目录下新建.eslintrc.js,代码如下:

module.exports = {
  parser: "babel-eslint",
  extends: "airbnb-base",
  env: {
    browser: true,
    node: true
  }
};

我们在packagejson中新建一条命令:

"scripts": {
    "eslint": "eslint ./lib --fix"
  },

webpack.base.js代码:

const path = require('path');
const glob = require('glob');

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

const setMPA = () => {
  const entry = {};
  const htmlWebpackPlugins = [];
  const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));

  Object.keys(entryFiles).map((index) => {
    const entryFile = entryFiles[index];
    const match = entryFile.match(/src\/(.*)\/index\.js/);
    const pageName = match && match[1];

    entry[pageName] = entryFile;
    return htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        template: path.join(__dirname, `src/${pageName}/index.html`),
        filename: `${pageName}.html`,
        chunks: [pageName],
        inject: true,
        minify: {
          html5: true,
          collapseWhitespace: true,
          preserveLineBreaks: false,
          minifyCSS: true,
          minifyJS: true,
          removeComments: false,
        },
      }),
    );
  });

  return {
    entry,
    htmlWebpackPlugins,
  };
};

const { entry, htmlWebpackPlugins } = setMPA();

module.exports = {
  entry,
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name]_[chunkhash:8].js',
  },
  module: {
    rules: [
      {
        test: /.js$/,
        use: ['babel-loader'],
      },
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  [
                    'autoprefixer',
                    {
                      overrideBrowserslist: ['last 2 version', '>1%', 'ios 7'],
                    },
                  ],
                ],
              },
            },
          },
          {
            loader: 'px2rem-loader',
            options: {
              remUnit: 75,
              remPrecision: 8,
            },
          },
        ],
      },
      {
        test: /.(png|jpe?g|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: { name: '[name]_[hash:8].[ext]' },
          },
        ],
      },
      {
        test: /.(woff|woff2|eot|otf|ttf)$/,
        use: [
          {
            loader: 'file-loader',
            options: { name: '[name]_[hash:8].[ext]' },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css',
    }),
    new FriendlyErrorsWebpackPlugin(),
    function errorPlugin() {
      this.hooks.done.tap('done', (stats) => {
        if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') === -1) {
          process.exit(1); // 1表示错误码并退出
        }
      });
    },
    new CleanWebpackPlugin(),
  ].concat(htmlWebpackPlugins),
};

webpack.dev.js代码:

const { merge } = require('webpack-merge');
const webpack = require('webpack');
const baseConfig = require('./webpack.base');

const devConfig = {
  mode: 'development',
  plugins: [new webpack.HotModuleReplacementPlugin()],
  devServer: {
    contentBase: './dist',
    hot: true,
    stats: 'errors-only',
  },
  devtool: 'cheap-source-map',
};

module.exports = merge(baseConfig, devConfig);

webpack.prod.js代码:

const { merge } = require('webpack-merge');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
const cssnano = require('cssnano');
const baseConfig = require('./webpack.base');

const prodConfig = {
  mode: 'production',
  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: cssnano,
    }),
    new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: 'react',
          entry: 'https://unpkg.com/[email protected]/umd/react.production.min.js',
          global: 'React',
        },
        {
          module: 'react-dom',
          entry: 'https://unpkg.com/react-dom@18/umd/react-dom.production.min.js',
          global: 'ReactDOM',
        },
      ],
    }),
  ],
  optimization: {
    splitChunks: {
      minSize: 0,
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'all',
          minChunks: 2,
        },
      },
    },
  },
};

module.exports = merge(baseConfig, prodConfig);

webpack.ssr.js代码:

const { merge } = require('webpack-merge');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
const cssnano = require('cssnano');
const baseConfig = require('./webpack.base');

const prodConfig = {
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: 'ignore-loader',
      },
      {
        test: /\.less$/,
        use: 'ignore-loader',
      },
    ],
  },
  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: cssnano,
    }),
    new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: 'react',
          entry: 'https://unpkg.com/[email protected]/umd/react.production.min.js',
          global: 'React',
        },
        {
          module: 'react-dom',
          entry: 'https://unpkg.com/react-dom@18/umd/react-dom.production.min.js',
          global: 'ReactDOM',
        },
      ],
    }),
  ],
  optimization: {
    splitChunks: {
      minSize: 0,
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'all',
          minChunks: 2,
        },
      },
    },
  },
};

module.exports = merge(baseConfig, prodConfig);

package.json配置:

{
  "name": "builder-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "directories": {
    "lib": "lib",
    "test": "test"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "eslint": "eslint ./lib --fix"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-eslint": "^10.1.0",
    "eslint": "^7.32.0",
    "eslint-config-airbnb-base": "^15.0.0"
  },
  "dependencies": {
    "autoprefixer": "^10.4.15",
    "babel-loader": "^8.3.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.6.0",
    "cssnano": "^4.1.11",
    "express": "^4.18.2",
    "file-loader": "^6.2.0",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "glob": "^7.2.3",
    "html-webpack-externals-plugin": "^3.8.0",
    "html-webpack-plugin": "^4.5.2",
    "less": "^4.2.0",
    "less-loader": "^6.2.0",
    "mini-css-extract-plugin": "^1.0.0",
    "optimize-css-assets-webpack-plugin": "^5.0.8",
    "postcss": "^8.4.28",
    "postcss-loader": "^4.3.0",
    "prettier": "^2.8.8",
    "px2rem-loader": "^0.1.9",
    "raw-loader": "^0.5.1",
    "style-loader": "^2.0.0",
    "url-loader": "^4.1.1",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.3",
    "webpack-merge": "^5.9.0"
  }
}

由于我们最终是要发布到npm上,并且要执行构建,所以依赖需要安装到dependencies。

冒烟测试介绍和实际运用

冒烟测试 (smoke testing)

冒烟测试是指对提交测试的软件在进行详细深入的测试之前而进行的预测试,这种
预测试的主要目的是暴露导致软件需重新发布的基本功能失效等严重问题。

冒烟测试执行

构建是否成

每次构建完成 build 目录是否有内容输出

  • 是否有 JS、CSS 等静态资源文件
  • 是否有 HTML 文件

判断构建是否成功

在示例项目里面运行构建,看看是否有报错

安装rimraf插件

npm i rimraf -D

示例代码:

const path = require("path");
const webpack = require("webpack");
const { rimraf } = require("rimraf");

process.chdir(path.join(__dirname, "template"));

rimraf("./dist")
  .then(() => {
    const prodConfig = require("../../lib/webpack.prod.js");

    webpack(prodConfig, (err, stats) => {
      if (err) {
        console.error(err);
        process.exit(2);
      }

      console.log(
        stats.toString({
          colors: true,
          modules: false,
          children: false
        })
      );

      console.log("webpack build success, run test start...");
    });
  })
  .catch((err) => {
    console.error(err);
  });

判断基本功能是否正常

编写 mocha 测试用例

  • 是否有 JS、CSS 等静态资源文件
  • 是否有 HTML 文件

安装插件glob-allmocha

npm i glob-all mocha -D

编写一个检测html的html-test.js:

const glob = require("glob-all");

describe("checking generated html files", () => {
  it("should generate html files", (done) => {
    const files = glob.sync(["./dist/index.html", "./dist/search.html"]);

    if (files.length > 0) {
      done();
    } else {
      throw new Error("no html files generated");
    }
  });
});

编写一个检测css、js的css-js-test.js:

const glob = require("glob-all");

describe("checking generated html files", () => {
  it("should generate html files", (done) => {
    const files = glob.sync(["./dist/index_*.js", "./dist/index_*.css", "./dist/search_*.js", "./dist/search_*.css"]);

    if (files.length > 0) {
      done();
    } else {
      throw new Error("no css js files generated");
    }
  });
});

最后测试代码:

const path = require("path");
const webpack = require("webpack");
const { rimraf } = require("rimraf");
const Mocha = require("mocha");

const mocha = new Mocha({
  timeout: "10000ms"
});

process.chdir(path.join(__dirname, "template"));

rimraf("./dist")
  .then(() => {
    const prodConfig = require("../../lib/webpack.prod.js");

    webpack(prodConfig, (err, stats) => {
      if (err) {
        console.error(err);
        process.exit(2);
      }
      console.log(
        stats.toString({
          colors: true,
          modules: false,
          children: false
        })
      );

      console.log("webpack build success, run test start...");

      mocha.addFile(path.join(__dirname, "html-test.js"));
      mocha.addFile(path.join(__dirname, "css-js-test.js"));
      mocha.run();
    });
  })
  .catch((err) => {
    console.error(err);
  });

最后使用node执行这个代码js即可。

单元测试和测试覆盖率

测试框架

单纯的测试框架(mocha),需要断言库

  • chai
  • should.js
  • expect
  • better-assert

集成框架,开箱即用

  • Jasmine
  • Jest

编写单元测试用例

  • 技术选型:Mocha + Chai
  • 测试代码:describe, it, except
  • 测试命令:mocha add.test.js

add.test.js示例代码:

const expect = require('chai').expect;
const add = require('../src/add');
describe('use expect: src/add.js', () => {
    it('add(1, 2) === 3', () => {
        expect(add(1, 2).to.equal(3));
    });
});

单元测试接入

mocha官网:https://mochajs.org/,官网示例代码:

var assert = require('assert');
describe('Array', function () {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal([1, 2, 3].indexOf(4), -1);
    });
  });
});
  • 1、安装 mocha + chai
npm i mocha chai -D
  • 2、新建 test 目录,并增加 index.js 单位测试文件入口。
const path = require("path");

process.chdir(path.join(__dirname, "smoke/template")); // 修改当前工作目录

describe("builder-webpack test case", () => {
  require("./unit/webpack-base-test.js");
});
  • 3、test目录下新建unit目录,存放单元测试文件。

安装断言插件assert:

npm i assert -D

例如test/unit/webpack-base-test.js代码:

const assert = require("assert");

describe("webpack.base.js test case", () => {
  const baseConfig = require("../../lib/webpack.base.js");

  it("entry", () => {
    assert.equal(baseConfig.entry.index, "D:/builder-webpack/test/smoke/template/src/index/index.js");
    assert.equal(baseConfig.entry.search, "D:/builder-webpack/test/smoke/template/src/search/index.js");
  });
});
  • 4、在 package.json 中的 scripts 字段增加 test 命令
"scripts": {
    "test": "./node_modules/mocha/bin/_mocha"
},

mocha默认会执行工程目录下的test文件下的index.js。

其中

  • 5、执行测试命令
npm run test

结果:
image

测试覆盖率

使用istanbul工具。istanbul 是一个 JavaScript 的代码覆盖率检查工具。

特征:

  • 可检查包括语句、分支和函数覆盖,以及反向工程的代码行覆盖
  • 模块加载钩子 可随时跟踪代码
  • 命令行工具 可运行带覆盖率检查的 node 单元测试,不需要对测试运行进行协作
  • 可生成 HTML 和 LCOV 报表
  • 可作为中间件使用,在浏览器进行测试
  • 可在命令行中以库的形式使用
  • 基于 esprima 解析器和 escodegen 代码生成器

官网地址:https://github.com/gotwarlost/istanbul

安装:

npm i istanbul -D

当然也可以全局安装。

基本用法:

$ cd /path/to/your/source/root
$ istanbul cover test.js

例如上面单元测试入口scripts调整test命令:

"scripts": {
    "test": "istanbul cover ./node_modules/mocha/bin/_mocha"
},

执行npm run test 结果如图:
image

说明:

  • Statements:覆盖的语句
  • Branches:覆盖的分支
  • Functions:覆盖的函数
  • Lines:覆盖的行数

持续集成和Travis CI

持续集成的作用

优点:

  • 快速发现错误
  • 防止分支大幅偏离主干

核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。

Github 最流行的 CI

image

接入 Travis CI

  • 1、首先 在github上创建一个新项目,上传存放的工程代码,创建示例:
    image

  • 2、https://travis-ci.org/ 使用 GitHub 账号登录

目前使用这个Travis CI现在已经改变运营策略,对开源项目也收费了。

travis.yml 文件内容

  • install 安装项目依赖
  • script 运行测试用例
language: node_js

sudo: false

cache:
    apt: true
    directories:
        - node_modules
        
node_js:stable #设置相应的版本

install:
    -npm install-D #安装构建器依赖
    -cd ./test/template-project
    - npm install -D #安装模板项目依赖

script:
    - npm test

发布构建包到npm社区

添加用户: npm adduser

升级版本

  • 升级补丁版本号:npm version patch
  • 升级小版本号:npm version minor
  • 升级大版本号:npm version major

发布版本:npm publish

具体操作:

  • 1、先去github上拷贝创建的新项目到本地。
  • 2、然后把本地的工程代码拷贝到git目录工程下。
  • 3、npm login登录到npm上

若果以前用的淘宝镜像,那么需要切换回npm.

npm config set registry https://registry.npmjs.org/

然后再执行 npm login,后面输入账号密码以及游戏登录进去
-4 、npm publish 发布到npm上

Git 提交规范和changelog生成

良好的 Git commit 规范优势:

  • 加快 Code Review 的流程
  • 根据 Git Commit 的元数据生成 Changelog
  • 后续维护者可以知道 Feature 被修改的原因

提交格式要求:

  • feat:新增feature
  • fix:修复bug
  • docs:仅仅修改了文档,比如README,CHANGELOG, CONTRIBUTE等等
  • style:仅仅修改了空格、格式缩进、都好等等,不改变代码逻辑
  • refactor:代码重构,没有加新功能或者修复bug
  • perf:优化相关,比如提升性能、体验
  • test:测试用例,包括单元测试、集成测试等
  • chore:改变构建流程、或者增加依赖库、工具等
  • revert:回滚到上一个版本

本地开发阶段增加 precommit 钩子:

  • 安装 husky
  • 通过 commitmsg 钩子校验信息
  • npm install husky --save-dev
"scripts": {
    "commitmsg": "validate-commit-msg",
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
},
"devDependencies": {
    "validate-commit-msg": "^2.11.1",
    "conventional-changelog-cli": "^1.2.0",
    "husky": "^0.13.1"
}

遵守 semver 规范

概念:语义化的版本控制(Semantic Versioning),简称语义化版本,英文缩写为 SemVer

优势:

  • 避免出现循环依赖
  • 依赖冲突减少

语义化版本(Semantic Versioning)规范格式

  • 主版本号: 做了不兼容的 API 修改(进行不向下兼容的修改)
  • 次版本号: 做了向下兼容的功能性增加(API 保持向下兼容的新增及修改)
  • 修订号: 做了向下兼容的问题修正(修复问题但不影响 API)

标签:npm,webpack4,const,require,js,webpack,test,编写
From: https://www.cnblogs.com/moqiutao/p/18415665

相关文章

  • 【webpack4系列】webpack构建速度和体积优化策略(五)
    目录速度分析:使用speed-measure-webpack-plugin体积分析:使用webpack-bundle-analyzer使用高版本的webpack和Node.js多进程/多实例构建资源并行解析可选方案使用HappyPack解析资源使用thread-loader解析资源多进程并行压缩代码方法一:使用parallel-uglify-plugin插件方法......
  • 【webpack4系列】webpack进阶用法(三)
    自动清理构建目录产物webpack4.x使用clean-webpack-plugin@3版本:npmiclean-webpack-plugin@3-Dwebpack配置:const{CleanWebpackPlugin}=require('clean-webpack-plugin')plugins:[newCleanWebpackPlugin(),]PostCSS插件autoprefixer自动补齐CSS3前缀需......
  • 【webpack4系列】webpack初识与构建工具发展(一)
    为什么需要构建工具?转换ES6语法转换JSXCSS前缀补全/预处理器压缩混淆图片压缩前端构建演变之路ant+YUIToolgruntgulp、fis3webpack、rollup、parcel为什么选择webpack?社区⽣态丰富配置灵活和插件化扩展官⽅方更新迭代速度快初识webpack,简单的例子入手......
  • Python编写简单登录系统的完整指南
    在现代应用中,用户认证和登录系统是一个非常重要的功能。通过登录系统,应用能够识别用户的身份,并为其提供相应的权限和服务。本文将介绍如何使用Python编写一个简单的登录系统,包括用户注册、登录验证、密码加密等功能。通过这一教程,将学习如何构建一个基本的用户登录系统,并理解其......
  • UG 二次开发-菜单与工具条(二、 应用DLL入口编写)
    在上一篇中,我们编写了UG加载DLL入口写入,这一篇,我们将继续编写应用DLL入口,上一篇中,最后是入加DLL,加载应用DLL,并入口函口数。首先,我们新建应用DLL主工程,工程名称我们就叫做IUGMain。完成工程创建。添加一个新的CPP,作为DLL的主程式文件由于我们这是由空白工程创建的应用......
  • Rust编写wasm入门
    创建项目cargonew--libmy-wasm添加依赖Cargo.toml[dependencies]wasm-bindgen = "0.2"[lib]crate-type = ["cdylib"]编写代码src/lib.rsuse wasm_bindgen::prelude::*;#[wasm_bindgen]pub fn add(a: i32, b: i32) -> i32 {       a + b......
  • Python语言如何编写函数?
    Python函数是指组织好的、可重复使用的、用来实现单一或相关联功能的代码段。Python函数包含系统中自带的一些函数、第三方函数、以及用户自定义的函数,那么Python如何编写函数?我们通过这篇文章来介绍一下。函数是一组可重复使用的代码块,用于执行特定的任务。它们可以接受......
  • 深度解析:投标方案如何编写才能赢得专家的青睐?
    文章目录变更记录前言一、千呼万唤始出来——服务方案组成二、柳暗花明又一村——服务方案源起2.1《技术评审标准表》与《技术评审索引表》2.2《技术要求》与《技术指标参数响应偏离表》三、书同文,车同轨——编写前准备3.1统一软件3.2统一格式3.2.1章节标题3.2.2......
  • 《网络安全应急管理与技术实践》 030-网络安全应急技术与实践(应急预案的编写)
    一、应急预案的编写1.应急响应预案的编制1.1总则1.2角色及职责1.3预防和预警机制1.4应急响应流程1.4.1事件通告1.4.2事件分类与定级1.4.3应急处置方式和原则1.4.4后期处置1.5应急响应保障措施1.6附件原创愚公搬代......
  • 从零开始一步一步搭建 Vue3 + Webpack5 项目脚手架指南
    **......