In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
Today, I will talk to you about how to customize the configuration of Webpack and loader processing under Angular CLI. Many people may not know much about it. In order to make you understand better, the editor has summarized the following for you. I hope you can get something according to this article.
1 Angular uses custom Webpack configuration method 1.1 background
After using Angular CLI to create a new project, one-click configuration can already meet most of the requirements, but for individual requirements, you may want to configure some additional loader or plugins for webpack. [recommended for related tutorials: "angular tutorial"]
1.2 replace Builder to implement external configuration of webpack
Angular.json exposes a variety of interfaces that can be replaced by Builder, and you can replace builder if you need to use a custom webpack configuration. @ angular-builders/custom-webpack and ngx-build-plus both provide the corresponding builder. There are many trend custom-webpack users who view npm. Here we take custom-webpack as an example to describe how to modify angular.json to use the custom webpack configuration.
1.3 install the package for Builder
Since @ angular-builders/custom-webpack is not an official ng package, you need to install it before using it:
Npm install @ angular-builders/custom-webpack
Different versions of ng need to install packages corresponding to different versions. At present, most libraries of ng have a good habit of matching the major version number of ng with the major version number of ng. For example, if you are using ng12, use the version of custom-webpack@12. So why do you need so many versions? the reason is that the content and structure of the @ angular-devkit/build-angular package and even the schema structure and location that ng uses by default under its own versions may change. For custom-webpack, it is more likely to inherit the schema and code of build-angular, and expose the modification entry of webpack, so that users do not need to know the whole webpack configuration to configure their desired functions locally.
1.4 configuration method
In the angular.json file, replace @ angular-devkit/build-angular with @ angular-builders/custom-webpack, which mainly includes builder of browser, dev-server, karma and other different links, and add configuration parameters
"build": {"builder": "@ angular-builders/custom-webpack:browser", "options": {/ / the following is the new configuration customWebpackConfig "customWebpackConfig": {"path": "scripts/extra-webpack.config.js"},.... }, "configurations":...}
Path can be specified according to its own project. This file can export a function (which will be called) or a webpack configuration (which will be Merge Options).
In terms of usage, the function is more flexible and can directly manipulate the entire webpack configuration. Sample file content
/ / extra-webpack.config.jsmodule.exports = (config) = > {/ / do something.. Return config;}
At this point, the basic steps required for the extended configuration of webpack are complete.
2 use custom Webpack configuration case-loader article 2.1 case 1: use the PostCSS plug-in to deal with CSS degraded interpolation (themed degraded) background
The theming of the component library adopts the css-var scheme for theming customization, and the function of changing the theme color is achieved by replacing the style: the value of the css custom attribute in root. For IE, it does not recognize and cannot parse values with var, so it will appear colorless. In order to satisfy gradual enhancement and elegant degradation as much as possible. We need to do some compatibility so that IE can display colors properly even if it cannot be themed.
Goal:
Color: var (--devui-brand, # 5e7ce0);-> color: # 5e7ce0; color: var (--devui-brand, # 5e7ce0)
Context:
In order to regulate the use of colors, Curry uses the scss variable to constrain. Such as $devui-brand: var (--devui-brand, # 5e7ce0), this kind of writing can satisfy the degradation of modern browsers. When the css custom attribute of-- devui-brand is not found, it will fall back to the later color value, but IE can't read the color value because it doesn't know var. The style file reference for the component is the definition file and then directly uses $devui-brand as the value, as follows
@ import'~ ng-devui/styles-var/devui-var.scss';.custom-class {color: $devui-brand;}
The default compilation is:
.custom-class {color: var (--devui-brand, # 5e7ce0);} solution
Now that the target is known, this becomes much easier. Looking at the webpack configuration started by the default NG project through console.log, you can see that there are two rule in module responsible for processing SCSS and SASS files. They both have test: /\ .scss $|\ .sass $/ field, and one is responsible for global scss compilation (the path set of style configured in angular.json is specified through the include field) One is responsible for the handling of scss references within components outside the global (scss that has been previously processed globally is excluded through the exclude field).
Usually the first idea might be to deal with sass, inserting its original value in front of the $devui-brand. However, because the sass variable itself may be assigned twice, such as $my-brand: $devui-brand; color: $my-brand;, it is obviously not appropriate to interpolate if you encounter $devui-brand, and the repeated definition of $my-brand will only make the last value take effect.
To change the way of thinking, when scss is expanded to css, the location of each value is determined, even if the place of the second assignment is the same final value. At this point, you can use a script to write the downgrade of IE, that is, what is written by the target.
Then, we can add a loader to deal with the css after sass-loader processing. The use of PostCSS to deal with css can be more rigorous in checking the grammatical structure.
Finally, the modified code is as follows:
/ / webpack-config-add-theme.jsfunction 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
The general logic of the code is to find the rule whose test is less/sass regular, find the location of less-loader/sass-loader in the loader in the corresponding use, and then add a PostCSS plug-in with custom add-origin-varvalue in front of its array position. (note: there is a piece of logic here to find the location of sass-loader. There are two equations because the loader of ng7,8 and ng9 users is different. Before, ng7 uses a string, and then ng9 uses a file path.)
The PostCSS plug-in is as follows:
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)});}})
The general logic of the code is as follows: a plug-in is defined through postcss.plugin, which iterates through every declarion (declaration) of css, if it is not a comment, and its value (for each css declaration, the left colon is called decl.prop in property,postcss. The right is called decl.value in value,postcss) exactly matches the regular rule (here the regular rule starts with-- devui-), insert the rule in front of the rule and replace the value with the value after the comma of the original rule.
Finally, it is mounted to extra-webpack-config.
/ / extra-webpack.config.jsconst webpackConfigAddTheme = require ('. / webpack-config-add-theme'); module.exports = (config) = > {return webpackConfigAddTheme (config);}
So far, we have achieved our goal, and the scope of interpolation is limited to-- only those that begin with devui need interpolation to avoid other var that do not want to be processed.
Main points:
Find the location of CSS processing. Sass has the problem of variable dependency, which is more suitable for processing in compiled css files.
Master the simple writing of the PostCss plug-in, and the sourceMap option remains the same
Note that the processing order of the loader is that the original data is received from the last loader in the use and passed on to the previous loader, and the front loader is responsible for the presentation of the final content.
2.2 case 2: read configuration context processing: SCSS/LESS processing TS alias path synchronization processing (alias module joint tuning) background
For the reference of the demo of the component library to the component, we implement the alias reference of ts through the alias in tsconfig, and build the library separately during the website production construction phase, first build the library, and then configure another tsconfig to point to the built library (no longer directly to the source code).
On the one hand, it makes the usage of demo look consistent with the business, on the other hand, the separate construction of demo to implement the production-side component library is completely consistent with the business usage, reducing the problem that has not been exposed in advance because of some nuances between webpack build and ng-packagr build.
This is already possible through tsconfig and different configuration for configuring build, but only for ts files, the exported scss file / less file does not take effect (scss and less files are exported because of the support for the use of external themed variables).
Goal: sass and less files implement the same reference path as the ts alias.
Context:
There are two configuration configured in the existing angular.json, one using the default tsconfig.app.json and the other a separately built tsconfig.app.separate.json.
Angular.json is as follows:
Tsconfig.app.json inherits tsconfig.json with the following alias configuration
{.... "compilerOptions": {"paths": {"ng-devui": ["devui/index.ts"], "ng-devui/*": ["devui/*"]}}.}
Tsconfig.app.separate.json inherits tsconfig.app.json and overrides the path field
{.... "compilerOptions": {"paths": {"ng-devui": [". / publish"], "ng-devui/*": [". / publish/*"]}}.}
So when npm run start (ng serve), the file will be read directly from the ts directory and built directly through webpack. The compilation speed is fast.
When npm run build:prod (ng build-- prod-- configuration separate), look for components with the same directory structure as the npm package from the directory built by the component. / publish/.
The above is the whole different environment with different ts configurations to achieve different builds, you can see that ts aliases play a very important role here.
However, .scss and .less files are also exported in the package of our NPM binary library. In demo, we can simply use the ts alias' ng-devui' to reference the files in the. / devui directory, and it is very convenient to automatically reference the components in the. / publish directory in the production package.
It is written in the same way as the business side references the files in the node_modules directory in the code, and the final build is the same compilation path, shielding this layer of difference. However, sass and less files do not support referencing variables in the package, because when the ts file requests the sass file, the path processing at this level is handled by webpack, but after sass-loader takes over (the same is true for less-loader, only sass-loader is said here), the internal processing of sass file reference is for sass-loader to start an sass compiler instance to compile the contents of the sass file. The sass compiler instance also handles references between sass files directly.
Solution
Fortunately, sass-loader actually provides a configuration option for importer, so you can get some articles here.
Importer provides synchronous and asynchronous api, consider not blocking we use asynchronous api function (url, prev, done), it can directly return the content {content: string} or the actual path of the file {file: string}, and it stipulates that if null is returned, it means that it can not be found in this importer, it will continue to chain calls to find other importer.
This is a very critical point.
By reading the code of sass-loader itself, we can see that the importer passed to sass-loader will merge with the importer of its built-in wavy line (~), see Code 1, Code 2.
In other words, we can implement our own importer while still keeping the built-in wavy lines of sass-loader parsing to node_module syntax sugar.
As in case 1, we can find sass-loader in the rules of the module configured by webpack, and pass an array of importer to the sassOptions of its options, so that we can insert the importer.
So the next question is, how do we map the corresponding alias path from the runtime?
When Angular compiles, an AngularCompilerPlugin (require ('@ ngtools/webpack') .AngularCompilerPlugin) will be used to deal with the corresponding tsconfig file paths under different configuration of angular.json build, and a readConfiguration function (require ('@ angular/compiler-cli') .readConfiguration) is exported in AngularCompiler CLI to parse the last real configuration under tsconfig under the path.
The description file of tsconfig can be extended, and another tsconfig file can be extended. ReadConfiguration helps us solve the problem of merging extended tsconfig. In this way, you can get the path alias configuration in the tsconfig corresponding to the current runtime environment.
The next step is to simply take out the path data for a simple mapping, preserving the rules of wavy lines. We map ~ ng-devui to. / devui when developing locally, to. / publish directory when generating packaging, and to referencing from node_modules on the user side.
The final code is as follows:
/ / tsconfig-alias-importer.jsconst path = require ('path'); const readConfiguration = require (' @ angular/compiler-cli'). ReadConfiguration;function pathAlias (tsconfigPath) {const {baseUrl, paths} = readConfiguration (path.resolve (tsconfigPath)) .options; if (! paths) {return [] } return Object.keys (paths) .filter (alias = > alias.endsWith ('/ *')) .map (alias = > ({alias: alias, paths:paths [alias]})) .map (rule = > ({aliasReg: new RegExp ('^ ~'+ rule.alias.replace (/\ * $/,'/ (. *?)'), pathPrefixes: rule.paths.map (pathname = > path.resolve (baseUrl | |'') Pathname.replace (/\ * $/,'))})) } module.exports = function getTsconfigPathAlias (tsconfigPath = 'tsconfig.json') {try {const rules = pathAlias (tsconfigPath); / / give the file return function importer (url, prev, done) {if (! rules | | rules.length = 0) {return null } for (let rule of rules) {if (rule.aliasReg.test (url)) {/ / only supports the first alias address for the time being, while others ignore const prefix = rule.pathPrefixes [0]; const filename = path.resolve (prefix, url.replace (rule.aliasReg, (item, match) = > match); return {file: filename} }} return null; / / return null if there is no match to continue using the next importer};} catch (error) {console.warn ('Sass alias importer might not effected', error); return function importer (url, prev, done) {return null;}
The general logic of the code is that the pathAlias function filters out the / * ending by reading the baseUrl and path in tsconfig (because the non-/ * ending mainly points to index.ts and will not be proxied to the style), and then integrates into pieces of regular and regular content to be replaced, such as this rule.
"ng-devui/*": [". / devui/*"]
Convert to via map
{alias: "ng-devui/*", paths: [". / devui/"]}
Further converted to
{aliasReg: / ^ ~ ng-devui\ / (. *?) /, pathPrefixes: "D:\\ code\\ ng-devui\ devui" / / the real path, the author uses the windows system here.
Here the outermost tsconfig of the baseUrl points to. /, that is, the root directory of the project, and finally the pathResolve will be resolved to the real path.
The exported importer of getTsconfigPathAlias, the sass, assumes that we have a file in the path. / devui/styles-var/devui-var.scss, then demo can use ~ ng-devui/styles-var/devui-var.scss when referencing, and the function detects multiple aliases one by one to match, if any url matches to regular. For example, if the reference address of demo matches / ^ ~ ng-devui\ / (. *?) /, then importer will return {filename: "D:\\ code\\ ng-devui\\ devui\\ styles-var\\ devui-var.scss"}. In this way, you can find the path alias configured in the tsconfig alias, which implements the alias referenced by the sass file.
The path alias of Tsconfig can fall back to multiple addresses. Here, it is simplified to only fall back to the first address. If you need to implement multiple addresses, you may need to insert multiple importer or check whether the file exists in one importer, and fall back to the second address and the third address. At this time, insert this importer into each sass-loader configured by webpack.
/ / webpack-config-sass-loader-importer.jsconst AngularCompilerPlugin = require ('@ ngtools/webpack'). AngularCompilerPlugin;const getTsConfigAlias = require ('. / get-tsconfig-alias'); function getAngularCompilerTsConfigPath (config) {const angularCompilerPlugin = config.plugins.filter (plugin = > plugin instanceof AngularCompilerPlugin). Pop (); if (angularCompilerPlugin) {return angularCompilerPlugin.options.tsConfigPath;} return undefined;} function webpackConfigSassImporterAlias (config) {const tsconfigPath = getAngularCompilerTsConfigPath (config) | | tsconfig.json' [{ruleTest: /\ .scss $|\ .sass $/, loaderName: 'sass-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 [insertPosition] .options.sassOptions.importer = [getTsConfigAlias (tsconfigPath)];});}); return config;} module.exports = webpackConfigSassImporterAlias
This code first finds the AngualrCompilerPlugin plug-in in the configuration of webpack, and then reads its tsconfig path at this time.
The above is the solution of sass path aliases, thanks to the fact that sass itself has an importer, but this problem is not so easy to solve on less. Less only provides the option of includePaths, which will traverse one by one to fall back, and treat all files equally, that is, all files will try one by one according to this includePaths. In fact, less does not support wavy lines, but less-loader supports wavy line syntax. Read the less-loader code to see if there are any clues. You can see that less-loader uses a plugin of WebpackFIleManagerment to supplement the suffix and use the resolve of webpack to make a fallback judgment. This logic is relatively complicated.
We can only change our way of thinking to solve this problem. Less itself has syntax, that is, we can determine which external files are referenced from the syntax. Before referencing, we can deal with the path, for example, treat ~ ng-devui/styles-var/devui-var.less as relative to the less file. /.. / styles-var/devui-var.less then the less compiler can understand.
This is the idea that we can borrow from the previous cases, add a loader before less-loader loading, deal with the import reference statement in less syntax in advance, and directly replace the wavy address with the address of our real development environment or production packaging environment.
Const AngularCompilerPlugin = require ('@ ngtools/webpack'). AngularCompilerPlugin;const pathAlias = require ('. / get-path-alias-from-tsconfig'); function getAngularCompilerTsConfigPath (config) {const angularCompilerPlugin = config.plugins.filter (plugin = > plugin instanceof AngularCompilerPlugin). Pop (); if (angularCompilerPlugin) {return angularCompilerPlugin.options.tsConfigPath;} return undefined;} function webpackConfigSassImporterAlias (config) {const tsconfigPath = getAngularCompilerTsConfigPath (config) | 'tsconfig.json' [{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 + 1,0, {loader: require.resolve ('. / less-alias-replacer-loader'), options: {aliasMap: pathAlias (tsconfigPath)}});}});}); return config;} module.exports = webpackConfigSassImporterAlias
This is a modification of webpack, and the code roughly means to find the less-loader and add a custom loader later, and pass the path alias from the tsconfig's data to the loader as options.
PathAlias is written in the same way as the previous sass-loader.
/ / get-path-alias-from-tsconfig.jsconst path = require ('path'); const readConfiguration = require (' @ angular/compiler-cli'). ReadConfiguration;module.exports = function pathAlias (tsconfigPath) {const {baseUrl, paths} = readConfiguration (path.resolve (tsconfigPath)) .options; if (! paths) {return [] } return Object.keys (paths) .filter (alias = > alias.endsWith ('/ *')) .map (alias = > ({alias: alias, paths: paths [alias]})) .map (rule = > ({aliasReg: new RegExp ('^ ~'+ rule.alias.replace (/\ * $/,'/ (. *?)'), pathPrefixes: rule.paths.map (pathname = > path.resolve (baseUrl | |'') Pathname.replace (/\ * $/,'))})) } const path = require ('path'); const {getOptions} = require (' loader-utils'); const validateOptions = require ('schema-utils'); const postcss = require (' postcss'); const postcssLessSyntax = require ('postcss-less'); const loaderName =' less-path-alias-replacer-loader';const trailingSlashAndContent = / [/\] [^ /\] *? $/ Const optionsSchema = {type: 'object', properties: {aliasMap: {anyOf: [{instanceof:' Array'}, {enum: [null]}]},}, additionalProperties: false} const defaultOptions = {} function getOptionsFromConfig (config) {const rawOptions = getOptions (config) if (rawOptions) {validateOptions (optionsSchema, rawOptions, loaderName) } return Object.assign ({}, defaultOptions, rawOptions) } / * * @ param {*} css less text content * @ param {*} aliasMap alias rule collection * / 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) / remove single quotation marks and double quotation marks const rule = aliasMap.filter (rule = > rule.aliasReg.test (oFilename)). Pop (); if (rule) {const prefix = rule.pathPrefixes [0]; / / ignore the remaining const filename = path.resolve (prefix, oFilename.replace (rule.aliasReg, (item, match) = > match) in the first path) 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 (); / / get the topic data in the configuration file const aliasMap = getOptionsFromConfig (this) .aliasMap; let newSource = source; if (aliasMap.length > 0) {newSource = lessReplacePathAlias (source, aliasMap, this.resourcePath);} / return result this.callback (null, newSource, map); return newSource } exports.default = process
Here you can customize how to write wepack-loader. In fact, you can use postcss-loader with plugin of custom replacePathAlias and syntax of postcss-less. This demonstrates how to write wepack-loader.
The main idea of the code is that an optionSchema of loader is defined to verify the option. After getting the path alias data in option, the process function uses postcss to process the code if the path alias has data. Notice that this points to the context of webpack in process, so you can get the current file path from resourcePath.
The core of the code is the middle postcss plug-in, by traversing the rules starting with @, reading the file name without single or double quotes if it is an import declaration, testing whether the file name hits any one of the rules, taking the first one, getting the absolute path of the file as handled by sass-loader, and then recalculating the relative path to the current file through path.relative. Then replace the @ import rule with the new file address.
In fact, this idea also applies to the processing of sass rules, only by replacing the syntax syntax with postcss-sass.
We can see that the first two big cases are dealing with css problems, and most of the time postcss can be used to deal with them. Directly manipulating the css content is easy to mistakenly modify the content, and the traversal after the AST syntax tree disassembly will be more secure.
Explain why sass and less are used at the same time. In general, projects do not use both at the same time. In fact, our project mainly uses sass. But for a packaged component library, the business will not perceive whether it is sass or less when using it, and it will not even provide sass or less files for business references. In order to be able to radiate the theme, the component library provides both sass and less version variables for use.
Postscript
Scripts for these two style compilers, loader, which support path aliases, were first written in August 2020.
Webpack officially stated in the sass-loader/less-loader usage document that ~ the syntax has been abolished and is recommended to be deleted.
Sass-loader@11.0.0 (2021-2-5) and less-loader@8.0.0 (2021-2-1) respectively issued the corresponding version declaration ~ has been marked as Deprecated.
As a historical solution, this case will still be placed here to provide some solutions.
The author thinks that it is different from the current fallback solution, especially when there are files with the same name (at present, few people use the module path with the same name as the relative directory path). The wavy line scheme can still clearly emphasize the first direction of the file.
Webpack officials began adding the webpackImporter option to less-loader at the end of August 2020, further shielding the differences between less-loader and sass-loader. Compatible with historical reasons, the two loader currently retain wavy syntax. Because the ng version of the project lags behind the official version of NG, the loader used by the official version of NG lags behind the official version of webpack. Less-loader@5.0.0,sass-loader@8.0.2 is still used in the NG9 version.
Main points:
Get tsconfig configuration items from the runtime
File path processing (scss, less)
Master the writing method of loader and receive parameters.
More solutions to supplement resolve.alias
Config.resolve.alias is also a place to configure aliases in Webpack configuration, and sass-loader/less-loader also supports the configuration of webpackImporter, which can be achieved directly by modifying the alias in config. For example:
Config.resolve.alias = {... Config.resolve.alias, 'ng-devui': path.resolve (' / devui/'),}) resolve.plugins:TsConfigPathsPlugin
So if webpack's resolve alias is already supported, is there no need to configure tsconfig, or how to configure it?
Unfortunately, the webpack and tsconfig of the Angular project are not synchronized, and both sides need to be configured at the same time, otherwise there will be a build error in which the module cannot be found. If the configuration of the two is different, the hidden trouble will be even greater, because the configuration of tsconfig directly affects the tsc compiler that cannot get the file, and webpack will interpret a copy, and so on.
Configure a copy that can be crammed into the alias by writing webpack-plugin to get the current tsconfig at run time. Because if you modify the config directly, then it is static, so it is better to get the current ts configuration file of the ng project.
Awesome-typescript-loader has a TsConfigPathsPlugin, which is also very easy to use.
Const {TsConfigPathsPlugin} = require ('awesome-typescript-loader'); module.exports = (config) = > {config.resolve.plugins = [. (config.resolve.plugins | | []), new TsConfigPathsPlugin ()]}
Add a TsConfigPathsPlugin to resolve.plugins and you can put it in tsconfig, but this plug-in has been archived, and the last version was 3 years ago. Here we have done a test, still does not support dynamic tsconfig, its code can be used to write a resolve plugin, dynamic tsconfig path acquisition can refer to our previous operation.
Finally, some new conclusions are added.
The resolve.alias of Webpack config and the alias of tsconfig are not synchronized. You need to configure two copies or find a method to synchronize.
The internal references of Sass-loader and less-loader can follow the resolve.alias of webpack. It can be said that the solution of resolve.alias is better than dealing with generality before changing the importer of sass-loader or before less-loader. Basically all blob can try to solve the path alias problem in this way. More attention still needs to be paid to the problem of dynamic tsconfig configuration acquisition.
2.3 case 3: modify the TerserPlugin exclusion, shake the tree to deal with the problem (tree shaking problem) background
When the business users of the component library first upgrade ng7, there are often problems with packaging, and many self-executing commands are considered to have no side effects after shaking the tree.
Goal: to troubleshoot the problem of shaking trees for individual directories without turning off the global shaking tree.
Context:
There is a configuration in Angular.json, which is turned on by default. After opening it, you can shake the tree to optimize the code of js type, thus reducing the package size.
Terser is a project from the Uglifyjs library fork to support ES6 syntax, which is used to compress js code during Angular compilation.
Angular uses TerserPlugin for tree shaking, which is not effective due to problems such as decorators (see how Angular works). Angular provides an optimizer dedicated to marking comments for pure functions, so tree shaking is a two-phase model for the default Angular project.
The first phase is that Angular adds a @ angular_devkit/build_optimizer/webpack-loader to mark useless code in the js file when parsing resources (ng build-- prod); angular_devkit/build_optimizer introduces that its main function is tagging / * PURE * / function.
In the second stage, Angular configures Webpack with optimization.minimizer array when generating packaging, stuffing TerserPlugin into the array, and then shakes out the non-referenced code with no side effects. (to be exact, two Plugin are plugged in, one for globalScript and one excluding globalScript. GlobalScript refers to the path configured in the script field of the build of angular.json. )
Solution
TerserPlugin itself has an include and exclude field (see API) that can be excluded with regularities and strings.
Function terserOptionsWebpackConfig (config) {let excludeList = [/ / you can fill in the directory you want to exclude here] 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
In the past, there were a lot of tree-shaking problems in the ng7 project, and there was no tree-shaking problem after some default configuration changes after upgrading to ng9. Including IVY packaging mode, the compilation engine actually shakes the tree in a different way, some files will not be shaken off, this problem still has to encounter specific analysis to solve. You can re-new a TerserPlugin if necessary, but keep its original options unchanged.
Main points:
The specific stage of shaking the tree, the problem of interception
Maintain the original Options without destroying it.
Case 4: background of automatic babelization of three-party ES2015 libraries (library processing of ES2015 only)
After highlight.js was upgraded to 10.0.0, ie11 was officially no longer supported by default, and only es2015 packages were exported. Previous business needs in order to be compatible with IE11, we need to babel highlight.js. After updating to ng9, by default, ng will only package es2015 and then differential packaging to es5, so there is no problem in production packaging, but there will be problems in the local development of ie11 debugging, such as class syntax ie does not understand and so on, resulting in the entire js cannot be loaded.
Goal: make packages that do not support es5 support es5 in the development state.
Context: since the previous version of component library 9 still needs to support ie11 for a period of time, that is, it often needs to debug in ie11 in development mode, so the problem that ie cannot be accessed due to highlight.js in development mode needs to be solved.
Solution
First of all, the classic way from es2015 to es5 is to ask babel to help with it. Then most of the new api in ES syntax can be solved by core-js, and the remaining IE11 and some browser-side DOM API implementations still need to add some polyfill. Polyfill can be used to add whatever api api, you can refer to here, this article will not repeat.
/ / babel-loader-wepack-config.jsconst 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 / * Note: if the third-party library only provides es6 version, add it to ES6_ONLY_THIRD_PARTY_LIST and convert it to es5 via babel conversion syntax * enabled only when target is es5 (such as npm start status) * differential packaging will be solved automatically, and there is no need to solve * / es6-only-third-party-list.js/** * if the third-party library only provides es6 version Then add it to ES6_ONLY_THIRD_PARTY_LIST and convert the syntax to es5 * / const path = require ('path') via babel 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
The general idea of the two pieces of code is to read from tsconfig, if the target is es5, then insert babel-loader, and then put the path of the corresponding three-party library into include.
The ES6_ONLY_THIRD_PARTY_LIST list indicates how the path to highlight.js should be written. If the compilation target is es2015, this processing will not be inserted, even differential packaging will not call it, and ng-cli will invoke the internal logic itself.
Postscript
IE11 has slowly retired from the stage of history, major websites have begun to declare that they no longer support IE11, and these redundant plug-ins can be slowly removed. Including ng12 is no longer committed to supporting ie, and these plug-ins are no longer necessary after upgrading to ng12.
Main points:
Distinguish between the degradation of syntax API and browser BOM/DOM gasket
Provide a maintainable list
Compile against the target context of tsconfig.
2.5 case 5: intercept modification automatically generate ts content (automatic content scan generation) case 5-1: automatically scan directory background
In a visual drag-and-drop productivity platform project, the component definition directory will have a series of repetitive directory structures that need to be summarized into a ts as a global information entry.
Goal: automatically scan the component definition directory and summarize the information into ts to avoid manually increasing information maintenance.
Context:
The content structure of the generated information summary is:
Src/app/connect/connet.ts
The structure of the directory is:
Src/component-lib
It is required not to include the contents of the _ directory.
Solution const fs = require ('fs'). Promises;const path = require (' path'); async function listDir () {return fs.readdir (path.resolve ('. / src/component-lib')). Then (dirs = > dirs.filter (item = >! item.startsWith ('_') / filter} function genConnectInfo (dirArr) {return `export const ConnectInfo = {${dirArr.map (item = > "'" + item + "': import (/ * webpackChunkName:\" component-lib- "+ item +"-connect\ "* / '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 ('. / src/component-lib')); / / automatically scan directories, but deleting directories may cause errors 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 ('. / src/app/connect')], enforce: 'post',}); return config;}; module.exports = webpackConfigAddScanAndGenerateConnectInfo
The directory scan content assembly is returned to src/app/connect/connet.ts through a simple loader.
Here are a few key points:
You need to use the enforce: 'post' attribute of loader, because the final loader of ts compilation goes directly to the file system to read the content, not to get the result from the previous loader to continue processing (it does not conform to the loader specification, but the performance will be better), so you need to configure the phase attribute to' post', to ensure that the final compiled content is what we generated.
Add some dependencies to loader to refresh the content when the directory structure changes, otherwise you will have to wait for the next startup to get the content, that is, this line this.addContextDependency (path.resolve ('. / src/component-lib'))
Because the tsc compilation of webpack is file analysis dependent, our dynamically generated file content, webpack can not analyze and rely on some other ts content, resulting in other ts content will not be compiled by ts. At this time, you can refer to the following and add the include field to tsconfing to solve the compilation problem.
/ * tsconfig.app.json*/ {"extends": ". / tsconfig.json", "compilerOptions": {"outDir": ". / out-tsc/app", "types": []}, "files": ["src/main.ts", "src/polyfills.ts"], "include": ["src/**/*.d.ts", "src/component-lib/**/*.ts"]}
Main points:
Automatic scanning logic
Add dependency
Add a compilation entry that is not directly dependent
Case 5-2: automatically scan css content background
The icon library exports a series of available font icons, which are referenced by different css class names. Now to make an icon selector, you need to list all the icons.
Goal: analyze the icon.css and extract all valid icon names.
Context:
The information needs to be automatically generated into a file called src/app/properties-panel/properties-control/icon-picker/icon-library.data.ts
Format as an array
The file of the icon library is. / node_modules/@devui-design/icons/icomoon/devui-icon.css
The format is:
The red mark in the picture is the icon name to be extracted.
Solution
Definition of loader
/ / auto-gen-icon-data-webpack-loader.jsconst 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);});}
Stuffing webpack config with 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 ('. / src/app/properties-panel/properties-control/icon-picker')], enforce: 'post',}) Return config;}; module.exports = webpackConfigAddGenIconData
The code is relatively simple.
Main points:
1. Analyze the file structure of icon library and eliminate some interference items in alignment.
2.6Additive: customize the debugging method of Webpack configuration loader
Configuration item debugging: print debugging in the method, and you can usually print out the content related to the configuration item before execution.
Content debugging: use a simple loader to print the content before and after loader, customize a print content loader, print before and after the implementation of custom loader, you can get the comparison content.
Some lessons: pay attention to the local development phase and production packaging phase when modifying loader. Don't lose sight of one or the other.
This paper introduces how to use the custom webpack setting in the project generated by AngularCLI, and gives several webpack configurations modified in order to solve the problem, covering the modification of css, the modification of js and the automatic scanning and generation of intercepted content.
This paper is mainly to solve the problem or functional features to modify the webpack configuration, does not involve complex construction speed optimization, performance optimization and so on.
Some of the content is compatible with the old version to deal with IE11 problems, the specific practice no longer has the significance of the replication scheme to solve practical problems, but more to provide some ideas and reference.
At present, several versions of Webpack always start from the entry file, load and parse the file through rule configuration, and intercept the entire build life cycle through plug-ins to make some extraction changes.
Most of the time, the default configuration of Angular can meet the requirements. When combined with tripartite library integration and automatic processing, using custom configuration can solve problems, save work and even further optimize performance, and so on.
After reading the above, do you have any further understanding of how to customize the configuration of Webpack and loader processing under Angular CLI? If you want to know more knowledge or related content, please follow the industry information channel, thank you for your support.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.