我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何自定义配置Angular CLI下的Webpack和loader处理

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何自定义配置Angular CLI下的Webpack和loader处理

今天就跟大家聊聊有关如何自定义配置Angular CLI下的Webpack和loader处理,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

1 Angular 使用自定义Webpack配置方法

1.1 背景

使用Angular CLI新建工程后,一键式的配置已经能满足大部分需求,但针对个体述求,可能会希望给webpack配置一些额外的loader或者plugins。【相关教程推荐:《angular教程》】

1.2 替换Builder实现外部配置webpack

angular.json 暴露了多种Builder可以替换的接口,如果需要使用自定义webpack配置可以替换一下builder。 @angular-builders/custom-webpackngx-build-plus都提供了对应的builder,查看npm的趋势custom-webpack用户比较多,这里以custom-webpack为例,介绍如何修改angular.json以用上自定义的webpack配置。

1.3 安装Builder的包

由于@angular-builders/custom-webpack并不是ng官方的包,所以使用前都需要先安装一下:

npm install @angular-builders/custom-webpack

不同的ng版本需要安装对应不同的版本的包, ng的大部分库目前有一个约定俗成的好习惯,就是主版本号和ng的主版本号是能够对上的。比如使用的是ng12,那就用custom-webpack@12的版本。那么为什么需要这么多版本,原因是ng在自己的不同版本下的默认使用的@angular-devkit/build-angular包的内容和结构甚至schema结构和位置可能会发生变化。对于custom-webpack来说更多是是继承build-angular的schema和代码,并暴露webpack的修改入口,让用户不需要了解整个webpack配置的情况下局部配置自己想要的功能。

1.4 配置方法

在angular.json文件中,替换@angular-devkit/build-angular@angular-builders/custom-webpack, 主要包括browser、dev-server、karma等几个不同环节的builder,并增加配置参数

 "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
               // 以下为新增的配置 customWebpackConfig
               "customWebpackConfig": {
                     "path": "scripts/extra-webpack.config.js"
               },
              ....
           },
          "configurations": ...
 },

path可以按自己的工程来指定。 该文件可以导出一个函数(将会被调用)或者一段webpack配置(将会被Merge Options)。

从使用情况来说函数灵活性更好,可以直接操作整个webpack配置。示例文件内容

// extra-webpack.config.js
module.exports = (config) => {
    //  do something..
   return config;
};

至此,webpack的扩展配置所需要的基础步骤就完成了。

2 使用自定义Webpack配置案例-loader篇

2.1 案例1:使用PostCSS插件来处理CSS降级插值(主题化降级)

背景

组件库主题化采用了css-var方案进行主题化定制,通过运行时替换样式:root里的css自定义属性的值来达到变更主题色的功能。对于IE来说它不认识也无法解析带var的值,那么它会表现为无颜色。为了尽量满足渐进增强和优雅退化。我们需要做一些兼容,以便IE无法使用主题化的情况下也能正常显示颜色。

目标:

color: var(--devui-brand, #5e7ce0); 
-> 
color: #5e7ce0; color: var(--devui-brand, #5e7ce0);

上下文:

为了规范颜色的使用,库里使用的是scss变量来约束。如$devui-brand: var(--devui-brand, #5e7ce0), 本身这种写法是能满足现代浏览器的降级的,当找不到--devui-brand的css自定义属性,会回落到后面的色值,但是IE不认识var所以无法读出色值。组件的样式文件引用是定义文件然后直接使用$devui-brand作为值,如下

@import '~ng-devui/styles-var/devui-var.scss';
.custom-class {
  color: $devui-brand;
}

默认编译完为:

.custom-class {
  color: var(--devui-brand, #5e7ce0);
}

解决方案

既然已经知道目标了,那么这件事情就变得简单多了,通过插桩(console.log)查看默认NG工程启动的webpack配置,可以看到module里有两个rule是负责处理SCSS和SASS文件的, 它们都拥有test: /\.scss$|\.sass$/字段,一个负责全局的scss的编译(通过include字段指定了配置在angular.json的style的路径集合),一个负责全局以外的组件内引用的scss的处理(通过exclude字段排除了前面全局已经处理过的scss)。

通常第一个想法可能是处理sass,遇到$devui-brand的地方前面插入一句它的原始值。但是由于sass变量本身可能被二次赋值,如$my-brand: $devui-brand; color: $my-brand;,这时候遇到$devui-brand的就插值的显然不合适,重复的定义$my-brand只是会最后一个值生效。

换个思路,当scss展开为css之后,每个取值的位置就是确定的了,哪怕二次赋值的地方也是同一个终值了。这时候就可以采用脚本来写IE的降级,也就是目标所写的内容。

那么,我们可以再sass-loader处理完之后增加一个loader来处理这段css。对css的处理使用PostCSS能对语法结构进行走查更严谨。

最后修改代码如下:

// webpack-config-add-theme.js
function webpackConfigAddThemeSupportForIE(config) {
  [{
    ruleTest: /\.scss$|\.sass$/,
    loaderName: 'sass-loader'
  }, {
    ruleTest: /\.less$/,
    loaderName: 'less-loader'
  }].forEach(({ruleTest, loaderName}) => {
    config.module.rules.filter(rule => rule.test + '' === ruleTest + '').forEach((styleRule) => {
      if (styleRule) {
        var insertPosition = styleRule.use.findIndex(loaderUse => loaderUse.loader === loaderName
          || loaderUse.loader === require.resolve(loaderName));
        if (insertPosition > -1) {
          styleRule.use.splice(insertPosition, 0, {
            loader: 'postcss-loader',
            options: {
              sourceMap: styleRule.use[insertPosition].options.sourceMap,
              plugins: () => {
                return [
                  require('./add-origin-varvalue'),
                ];
              }
            }
          });
        }
      }
    });
  });
  return config;
};
module.exports = webpackConfigAddThemeSupportForIE;

代码大致逻辑为寻找test为less/sass正则的rule,在对应的use里的loader里找到less-loader/sass-loader的位置,然后在其数组位置前面增加一个postcss-loader,loader里使用了自定义的add-origin-varvalue的PostCSS插件。(备注:这里有一块逻辑是找到sass-loader的位置, 这里有两个等式是因为ng7,8和ng9用户的loader写法不一样了,之前ng7用字符串,后面ng9用的是文件路径)

PostCSS插件如下:

var postcss = require('postcss');
var varStringJoinSeparator = 'devui-(?:.*?)';
var cssVarReg = new RegExp('var\\(\\-\\-(?:' + varStringJoinSeparator + '),(.*?)\\)', 'g');

module.exports = postcss.plugin('postcss-plugin-add-origin-varvalue', () => {
  return (root) => {
    root.walkDecls(decl => {
      if (decl.type !== 'comment' && decl.value && decl.value.match(cssVarReg)) {
        decl.cloneBefore({value: decl.value.replace(cssVarReg, (match, item) => item) });
      }
    });
  }
});

代码的大致逻辑如下,通过postcss.plugin定义了一个插件,该插件遍历css每一条declarion(声明),如果不是注释,且它的值(对于每一条css声明来说冒号左边称为property,postcss里为decl.prop;右边称为value,postcss里为decl.value)刚好匹配了正则规则(这里的正则规则为--devui-开头),则在这条规则的前面插入该规则且把值替换为原规则逗号后面的值。

最后挂载到extra-webpack-config里

// extra-webpack.config.js
const webpackConfigAddTheme = require('./webpack-config-add-theme');
module.exports = (config) => {
   return webpackConfigAddTheme(config);
};

至此我们达成了我们的目标,而且对插值的范围做了限定,限定为--devui开头的才需要插值,避免其他不想被处理的var被处理了。

要点:

  • 找准CSS处理的位置, sass存在变量依赖问题,更适合在编译后的css文件里处理

  • 掌握PostCss插件的简单写法, sourceMap选项维持不变

  • 注意loader的处理顺序,是从use里的最后一个loader接收原始数据不断往前面的loader传递,最前面的loader负责了最后内容的呈现。

2.2 案例2: 读取配置上下文处理:SCSS/LESS 处理TS别名路径同步处理 (别名模块联调)

背景

组件库的demo对组件的引用,我们通过tsconfig里的alias实现了ts的别名引用,并在网站生产构建阶段采用了分开构建,先构建库,然后配置另外的tsconfig指向了构建完的库(不再直接指向源码)。

一方面使得demo看起来用法和业务一致,另一方面分开构建实现生产端组件库的demo的使用方法和业务使用方法完全一致,减少因为webpack构建和ng-packagr构建出来后一些细微差别导致问题没有提前暴露出来。

这些通过tsconfig和配置build的不同的configuration已经可以实现了,但是仅仅只适用于ts文件,导出的scss文件/less文件就不生效了(由于支持外部主题化变量使用,scss文件和less文件会导出)。

目标: sass、less文件实现ts别名一样的引用路径。

上下文:

现有angular.json里配置了两个configuration,一个是使用默认的tsconfig.app.json,一个是分开构建的tsconfig.app.separate.json。

angular.json如下:

如何自定义配置Angular CLI下的Webpack和loader处理

tsconfig.app.json 继承了tsconfig.json有如下别名配置

{
   ....
"compilerOptions":{
    "paths": {
      "ng-devui": ["devui/index.ts"],
      "ng-devui
function lessReplacePathAlias(css, aliasMap, sourcePath) {
  const replacePathAlias = postcss.plugin('postcss-plugin-replace-path-alias', () => {
    return (root) => {
      root.walkAtRules(atRule => {
        if (atRule.import && atRule.filename) {
          const oFilename = atRule.filename.substring(1, atRule.filename.length - 1); // 去掉头尾单引号双引号 
          const rule = aliasMap.filter(rule => rule.aliasReg.test(oFilename)).pop();
          if (rule) {
            const prefix = rule.pathPrefixes[0]; // 取第一个路径忽略剩余的
            const filename = path.resolve(prefix, oFilename.replace(rule.aliasReg, (item, match) => match));
            const relativePath = path.relative(sourcePath.replace(trailingSlashAndContent, ""), filename).split(path.sep).join('/');
            var realPathAtRule = atRule.clone({ params: (atRule.options || '' )  + " '" + relativePath + "'", filename: "'" + relativePath + "'"});
            atRule.replaceWith(realPathAtRule);
          }
        }
      });
    }
  });
  return postcss([replacePathAlias]).process(css, { syntax: postcssLessSyntax }).css;
}

function process(source, map) {
  this.cacheable && this.cacheable();

  // 获取配置文件里的主题数据
  const aliasMap = getOptionsFromConfig(this).aliasMap;
  let newSource = source;
  if (aliasMap.length > 0) {
    newSource = lessReplacePathAlias(source, aliasMap, this.resourcePath);
  }
  // 返回结果
  this.callback(null, newSource, map);
  return newSource;
}

exports.default = process;

这里自定义一个wepack-loader的写法,实际上也是可以用postcss-loader搭配自定义replacePathAlias 的plugin 和 postcss-less的syntax进行使用。这里演示了wepack-loader的写法。

代码大意是定义了一个loader的optionSchema用于校验选项,process函数获取option里的路径别名数据之后,如果路径别名有数据则用postcss对代码进行处理。注意在process里this指向webpack的上下文,所以可以从resourcePath里获取当前文件路径。

代码核心为中间的postcss插件, 通过遍历@开头的规则,如果是一个import声明则读取文件名去掉头尾的单双引号;测试是否文件名命中了规则中的任意一条,命中则取第一条,和sass-loader处理的一样得到了文件的绝对路径,然后通过path.relative重新计算出和当前文件的相对路径。然后将这条@import规则替换成新的文件地址。

实际上这个思路同样适用于sass规则的处理,只需要把语法syntax换成postcss-sass。

我们可以看到前两个大案例都是在处理css类问题的,大部分时候处理都可以用上postcss利器。直接去操作css内容容易误修改内容,而经过AST语法树拆解后的遍历会更稳当一些。

说一下为什么会同时使用sass和less。一般工程是不会同时使用两种的。实际上我们的工程主要也是使用sass。但是对于一个打包后的组件库来说,业务使用的时候是不会感知它是sass还是less的,甚至也不会提供sass或者less文件给业务引用。这里是为了主题化的能力能够对外辐射,组件库同时提供了sass和less的版本变量供使用。

后记

这两个样式编译器loader支持路径别名的脚本最早写于2020年8月。

Webpack官方在sass-loader/less-loader的使用文档里面都说明了,~语法已经废除,建议删除。

sass-loader@11.0.0(2021-2-5),less-loader@8.0.0(2021-2-1)分别发布了对应版本声明~已经标记为Deprecated。

作为历史解决方案,这个案例依然会放在这里,提供一些解决思路。

笔者认为能~和现在的回落解决方案还是不一样的,尤其当存在同名文件的时候(目前这种情况会比较少,少有人使用模块同名路径作为相对目录路径),波浪线方案仍然能明显强调出文件的第一指向。

Webpack官方在2020年8月底开始给less-loader也加上了webpackImporter的选项,进一步屏蔽less-loader和sass-loader之间的差异。兼容历史原因,这两个loader目前还会保留波浪线语法。由于项目ng版本滞后于NG官方版本,NG官方版本使用的loader又滞后于webpack官方版本。目前NG9版本仍在用less-loader@5.0.0,sass-loader@8.0.2。

要点:

  • 从运行时获取tsconfig配置项

  • 文件路径处理(scss、less)

  • 掌握loader的写法,接收参数。

更多解法补充

resolve.alias

Webpack配置中 config.resolve.alias 也是一个配置别名的地方, 而且sass-loader/less-loader也支持了webpackImporter的配置,其实可以直接通过修改config中的alias就能达成目的。 比如:

config.resolve.alias = {
      ... config.resolve.alias,
      'ng-devui': path.resolve('./devui/'),
   })
resolve.plugins:TsConfigPathsPlugin

那么如果webpack的resolve alias已经支持了,是不是tsconfig就可以不用配置,或者怎么配置成一份?

不幸的是 Angular工程的 webpack 和 tsconfig不是同步的,两边需要同时配置,否则会出现找不到模块的构建错误, 如果两者配置不同,隐患会更大,因为tsconfig的配置是直接影响tsc编译器的拿不到文件,webpack也会解读出一份等。

配置成一份可以通过写webpack-plugin在运行时拿到当前的tsconfig再处理数据塞到alias里。因为如果直接修改config,那么它是静态的,实际情况还是需要获取到ng工程当前的ts配置是什么文件会比较好。

Awesome-typescript-loader有个TsConfigPathsPlugin,使用也非常简单。

const { TsConfigPathsPlugin } = require('awesome-typescript-loader');
module.exports = (config) => {
  config.resolve.plugins = [
      ... (config.resolve.plugins || []),
      new TsConfigPathsPlugin()
   ]
};

给resolve.plugins塞一个TsConfigPathsPlugin就可以把tsconfig里的,不过这个插件已经存档了,最后一个版本是3年前的了。这里我们做了一个测试,仍然是不支持动态的tsconfig,它的代码可以参考用来写一个resolve的plugin, 动态的tsconfig的path获取可以参考我们之前的操作。

最后补充几个新的结论
  • Webpack config的resolve.alias和 tsconfig的alias不是同步的,需要配置两份或者找到一个方法同步。

  • Sass-loader 和less-loader的内部引用都能走webpack的resolve.alias, 可以说resolve.alias的解法会比 在改sass-loader的importer或者在less-loader前先处理 通用性更好,基本上所有blob都可以尝试去这样解决路径别名问题。更多需要注意的仍然是动态的tsconfig的配置获取问题。

2.3 案例3:修改TerserPlugin排除项,摇树处理问题(摇树问题)

背景

组件库的业务使用方刚升级ng7的时候,打包经常出问题,经常出现文件摇树之后,很多自执行命令被摇树认为无副作用摇树掉了。

目标:不关掉全局摇树的情况下,针对个别目录进行摇树的问题排除。

上下文:

Angular.json里有个配置,默认为打开,打开之后可以对js类型的代码进行摇树优化,从而减小打包体积。

Terser是从Uglifyjs这个库fork 出来的项目用来支持ES6语法,Angular 编译阶段用它来压缩js代码。

Angular使用TerserPlugin来进行摇树,由于装饰器等问题导致摇树效果不理想的问题(相关讨论内容见Angular摇树如何工作),angular提供了一个专门用于标记纯函数的注释的优化器,所以对于默认的Angular项目来说,摇树是一个两阶段模型。

第一个阶段是Angular在生产构建阶段(ng build --prod)在解析资源的时候加入了一个@angular_devkit/build_optimizer/webpack-loader,用于标记js文件里的无用代码;angular_devkit/build_optimizer 介绍它的主要功能为标记功能。

第二个阶段是Angular在生成打包的时候给Webpack配置了 optimization.minimizer数组塞入了TerserPlugin然后把无副作用的无引用代码摇掉。(准确说是塞入了两个Plugin,一个针对globalScript,一个排除globalScript。GlobalScript是指在angular.json的build的script字段配置的路径。)

解决方案

TerserPlugin本身是有一个include和exclude字段(见API)可以用正则和字符串来排除。

function terserOptionsWebpackConfig(config) {
  let excludeList = [
    // 此处可以填自己要排除的目录
  ]
  let minimizerArr = config.optimization.minimizer;
  let terserPlugins = minimizerArr
    .filter(plugin => plugin.options && plugin.options.terserOptions)
  terserPlugins.forEach(terserPlugin => {
    if (terserPlugin.plugin.exclude) {
      const isArray = Array.isArray(terserPlugin.plugin.exclude)
      if (isArray) {
        terserPlugin.plugin.exclude = [
          ...terserPlugin.plugin.exclude,
          ...excludeList
        ];
      } else {
        terserPlugin.plugin.exclude = [
          terserPlugin.plugin.exclude,
          ...excludeList
        ];
      }
    } else {
      terserPlugin.plugin.exclude = excludeList;
    }
  });
  return config;
};

module.exports = terserOptionsWebpackConfig;

之前在ng7工程会遇到比较多的摇树问题,有些升级到ng9之后默认配置有点变化之后就没有摇树问题了。包括IVY打包模式下编译引擎实际上摇树的方式不太一样,有些文件不会被摇掉了,这个问题还是要遇到具体问题具体分析来解决。必要时可以重新new一个TerserPlugin但是要保持它的原来的options不变。

要点:

  • 摇树具体的阶段,拦截问题

  • 维持原有的Options,不对其进行破坏。

2.4 案例4: 三方ES2015库自动babel化处理(仅有ES2015的库处理)

背景

highlight.js升级到10.0.0之后,官方就开始不再默认支持ie11了,导出的包也只有es2015的包。之前的业务需要为了兼容IE11,我们需要对highlight.js进行一次babel。更新到ng9之后,其实ng默认会仅打包es2015然后进行差分打包到es5,所以实际情况在生产打包是没有问题的,但是在本地开发的ie11调试环节会出现问题,比如class的语法ie不认识等等,导致整个js无法加载。

目标:让不支持es5的包在开发态支持es5。

上下文:由于之前组件库9的版在一段时间内仍然需要支持ie11,也就经常需要开发态下到ie11下debug,所以开发态下的highlight.js导致ie无法访问问题需要解决。

解决方案

首先es2015转es5经典的做法就是让babel帮忙处理。然后大部分的ES语法新增的api可以由core-js来解决,剩下的IE11还有一些浏览器端DOM的API的实现还需要添加一些polyfill才行。Polyfill可以用到了什么api就加什么api,具体可以从这里参考,本文不再累述。

// babel-loader-wepack-config.js
const path = require('path');
const ts = require('typescript');
const AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
const readConfiguration = require('@angular/compiler-cli').readConfiguration;
const ES6_ONLY_THIRD_PARTY_LIST = require('./es6-only-third-party-list');

function getAngularCompilerTsConfigPath(config) {
  const angularCompilerPlugin = config.plugins.filter(plugin => plugin instanceof AngularCompilerPlugin).pop();
  if (angularCompilerPlugin) {
    return angularCompilerPlugin.options.tsConfigPath;
  }
  return undefined;
}
function getTsconfigCompileTarget(tsconfigPath) {
  const {target} = readConfiguration(path.resolve(tsconfigPath)).options;
  return target;
}

function webpackConfigAddBabel2ES5(config, list = []) {
  const tsconfigPath = getAngularCompilerTsConfigPath(config) || 'tsconfig.json';
  const target = getTsconfigCompileTarget(tsconfigPath);
  if (target === ts.ScriptTarget.ES5) {
    config.module.rules.push({
      test: /\.js$/,
      use: [{
        loader: 'babel-loader'
      }],
      include: [
        ...ES6_ONLY_THIRD_PARTY_LIST,
        ...list
      ]
    });
  }
  return config;
};
module.exports = webpackConfigAddBabel2ES5;

// es6-only-third-party-list.js

const path = require('path');
const ES6_ONLY_THIRD_PARTY_LIST = [
    path.resolve('./node_modules/highlight.js') // ^10.0.0 no longer support ie 11
];
module.exports = ES6_ONLY_THIRD_PARTY_LIST;

两段代码大概思路就是从tsconfig里面读,如果目标为es5,则塞进去babel-loader,然后把对应的三方库的路径放到include里边。

ES6_ONLY_THIRD_PARTY_LIST 列表示意了highlight.js的路径应该怎么写。如果编译目标为es2015,则这段处理就不需要了不会被插入,哪怕是差分打包也不会调用它,ng-cli会自行调用内部逻辑。

后记

IE11已经慢慢退出了历史舞台,各大网站也开始声明不再支持IE11,这些冗余的插件已经可以慢慢移除。包括ng12起也不再承诺支持ie,升级到ng12之后这些插件也没有必要了。

要点:

  • 分清语法API的降级和浏览器BOM/DOM垫片

  • 提供一个可维护的列表

  • 针对tsconfig的target上下文进行编译。

2.5 案例5: 拦截修改自动生成ts内容(内容自动扫描生成)

案例5-1: 自动扫描目录

背景

在可视化拖拽生产力平台项目,组件定义目录会有一系列重复雷同的目录结构,最后需要汇总到一个ts里作为全局信息入口。

目标:自动扫描组件定义目录,汇总信息到ts里,避免手动增加信息维护。

上下文:

生成的信息汇总内容结构为:

class="lazy" data-src/app/connect/connet.ts

如何自定义配置Angular CLI下的Webpack和loader处理

目录的结构为:

class="lazy" data-src/component-lib

如何自定义配置Angular CLI下的Webpack和loader处理

要求不要包含 _目录的内容

解决方案
const fs = require('fs').promises;
const path = require('path');

async function listDir() {
  return fs.readdir(path.resolve('./class="lazy" data-src/component-lib')).then(dirs => dirs.filter(item => !item.startsWith('_'))); // 过滤_开头的
}
function genConnectInfo(dirArr) {
  return `export const ConnectInfo = {
  ${dirArr.map(item =>"'"+ item +"': import(   'class="lazy" data-src/component-lib/" + item + "/connect')").join(`,
`)}
};`;
}
async function process() {
  var list = await listDir();
  return genConnectInfo(list);
}

module.exports = function(content, map, meta) {
  var callback = this.async();
  this.addContextDependency(path.resolve('./class="lazy" data-src/component-lib')); // 自动扫描目录,但是删除目录可能会引起报错
  process().then((result)=> {
    callback(null, result, map, meta);
  }, err => {
    if (err) return callback(err);
  });
};
const path = require('path');
function webpackConfigAddScanAndGenerateConnectInfo(config) {
    config.module.rules.push({
      test: /connect\.ts$/,
      use: [{
        loader: require.resolve('./scan-n-gen-connect-webpack-loader')
      }],
      include: [
        path.resolve('./class="lazy" data-src/app/connect')
      ],
      enforce: 'post',
    });
  
  return config;
};
module.exports = webpackConfigAddScanAndGenerateConnectInfo;

通过简单的一个loader 将目录扫描内容组装返回给class="lazy" data-src/app/connect/connet.ts。

这里有几个要点:

  • 这里需要使用loader的enforce: 'post'属性,因为ts的编译最后loader是直接去文件系统读取内容的不是从上一个loader拿到结果继续往下处理的(不符合loader的规范,但是性能会更好),所以这里需要把阶段属性配置为'post',确保最后编译的内容是我们生成的内容。

  • 对loader添加一些依赖可以在目录结构变化的时候刷新内容,否则就只能等下一次启动的时候获取内容,即这一行 this.addContextDependency(path.resolve('./class="lazy" data-src/component-lib'));

  • 由于webpack的tsc编译是文件分析依赖型的,我们动态生成的文件内容,webpack就无法从中分析依赖了另外一些ts内容,导致其他ts内容不会走ts编译。这时候可以参考下面,在tsconfing加一下include字段,解决编译问题。


{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": []
  },
  "files": [
    "class="lazy" data-src/main.ts",
    "class="lazy" data-src/polyfills.ts"
  ],
  "include": [
    "class="lazy" data-src*.d.ts",
    "class="lazy" data-src/component-lib*.ts"
  ]
}

要点:

  • 自动扫描逻辑

  • 添加依赖

  • 添加非直接依赖的编译入口

案例5-2: 自动扫描css内容

背景

图标库导出了一系列可用字体图标,图标通过不同的css类名来引用,现在要做一个图标选择器,需要把所有图标列出来。

目标:分析icon.css,提取所有有效的icon名字。

上下文:

需要把信息自动生成到一个文件叫 class="lazy" data-src/app/properties-panel/properties-control/icon-picker/icon-library.data.ts

格式为一个数组

如何自定义配置Angular CLI下的Webpack和loader处理

图标库的文件为./node_modules/@devui-design/icons/icomoon/devui-icon.css

格式为:

如何自定义配置Angular CLI下的Webpack和loader处理

图片里红色标记的就是要提取出来的图标名。

解决方案

loader的定义

// auto-gen-icon-data-webpack-loader.js
const fs = require('fs').promises;
const path = require('path');

function genIconData(fileContent) {
  const iconNames = [...fileContent.matchAll(/\.icon-(.*?):before/g)].map(item => item[1]);
  return `export const ICON_DATA = [
  ${iconNames.map(item => "'" + item + "'").join(",")}
];
`;
}
async function process(file) {
  const content = await fs.readFile(`${path.resolve(file)}`, 'utf8');
  return genIconData(content);
}

module.exports = function(content, map, meta) {
  const file = './node_modules/@devui-design/icons/icomoon/devui-icon.css';
  var callback = this.async();
  this.addDependency(path.resolve(file));
  process(file).then((result)=> {
    callback(null, result, map, meta);
  }, err => {
    if (err) return callback(err);
  });
};

webpack config里塞入loader

const path = require('path');
function webpackConfigAddGenIconData(config) {
   
    config.module.rules.push({
      test: /icon-library.data\.ts$/,
      use: [{
        loader: require.resolve('./auto-gen-icon-data-webpack-loader')
      }],
      include: [
        path.resolve('./class="lazy" data-src/app/properties-panel/properties-control/icon-picker')
      ],
      enforce: 'post',
    });
  
  return config;
};
module.exports = webpackConfigAddGenIconData;

代码相对就比较简单了。

要点:

1、分析图标库文件结构,排除一些对齐的干扰项

2.6 补充:自定义Webpack配置loader的调试方法

  • 配置项调试:在方法中进行打印调试, 通常在执行前就已经可以打印出配置项相关的内容。

  • 内容调试:使用简单的loader打印前后经过loader的内容, 自定义一个打印内容的loader,在执行自定义loader前后都打印一下,可以获得对比内容。

  • 一些经验: 修改loader的时候要注意本地开发阶段和生产打包阶段,不要顾此失彼。


文章介绍了如何在AngularCLI生成的工程里使用自定义的webpack设置,并且举了几个实际情况下为了解决问题修改的webpack配置,覆盖css的修改、js的修改以及拦截内容自动扫描生成。

本文主要为解决问题或者功能特性去修改webpack配置,没有涉及复杂的构建速度优化、性能优化等。

有些内容是旧版本处理IE11问题做兼容的,具体的实践不再具备复制方案就能解决现实问题的意义,更多的是提供一些思路和参考。

Webpack目前几个版本始终是从入口文件开始,通过规则配置对文件进行加载解析,通过插件拦截整个构建生命周期做一些抽取变换。

大部分时候Angular的默认配置已经能满足需求,当和三方库集成、自动化处理结合的时候,采用自定义的配置可以解决问题,节省工作甚至进一步优化性能等等。

看完上述内容,你们对如何自定义配置Angular CLI下的Webpack和loader处理有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网行业资讯频道,感谢大家的支持。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何自定义配置Angular CLI下的Webpack和loader处理

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何实现自定义配置网络ip、网关、dns的批处理文件

这篇文章主要为大家展示了“如何实现自定义配置网络ip、网关、dns的批处理文件”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何实现自定义配置网络ip、网关、dns的批处理文件”这篇文章吧。自定
2023-06-09

C++ 技术中的异常处理:如何为自定义异常定义和抛出错误码?

c++++ 异常处理中,自定义异常和错误码可提供更详细的错误信息。可定义派生自 std::exception 的异常类,包含描述性成员变量和函数,并使用 std::make_error_code() 函数抛出包含错误码的异常。在捕获异常后,
C++ 技术中的异常处理:如何为自定义异常定义和抛出错误码?
2024-05-09

如何强制Outlook解析缓存模式下的代理地址和自定义属性

要强制Outlook解析缓存模式下的代理地址和自定义属性,你可以按照以下步骤进行操作:1. 打开Outlook,并点击左上角的“文件”选项。2. 在打开的菜单中,选择“帐户设置”下的“帐户设置”选项。3. 在弹出的“帐户设置”窗口中,选择要
2023-09-07

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录