当前位置 : 主页 > 网络编程 > JavaScript >

手把手带你自定义配置Angular CLI下的Webpack和loader处理

来源:互联网 收集:自由互联 发布时间:2023-08-02
本篇文章通过案例介绍一下Angular CLI下的自定义Webpack配置方法和自定义loader处理的方法,希望对大家有所帮助! 1 Angular 使用自定义Webpack配置方法1.1 背景 使用Angular CLI新建工程后,一键

本篇文章通过案例介绍一下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如下:

1.png

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

{
   ....
"compilerOptions":{
    "paths": {
      "ng-devui": ["devui/index.ts"],
      "ng-devui/*": ["devui/*"]
    }
}
...
}

2.png

tsconfig.app.separate.json又继承了tsconfig.app.json并且覆写了path字段,

{
   ....
"compilerOptions":{
    "paths": {
      "ng-devui": ["./publish"],
      "ng-devui/*": ["./publish/*"]
    }
}
...
}

3.png

所以当npm run startng serve)的时候,会直接从ts目录读取文件,直接走webpack构建,编译速度快;

npm run build:prodng build --prod --configuration separate)的时候,会从组件构建的目录./publish/下找寻npm包同目录结构的组件。

上一篇:nodejs用什么调试工具
下一篇:没有了
网友评论