├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package.json ├── src └── main.ts ├── test ├── Roboto-Bold.woff2 ├── Roboto-Regular.woff ├── Roboto-Regular.woff2 ├── entry.js ├── expected-1.html ├── expected-2.html ├── index.test.ts ├── jest.config.js ├── setupTest.js └── style.css └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | .env.test 68 | 69 | # parcel-bundler cache (https://parceljs.org/) 70 | .cache 71 | 72 | # next.js build output 73 | .next 74 | 75 | # nuxt.js build output 76 | .nuxt 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless/ 83 | 84 | # FuseBox cache 85 | .fusebox/ 86 | 87 | # DynamoDB Local files 88 | .dynamodb/ 89 | 90 | # OS metadata 91 | .DS_Store 92 | Thumbs.db 93 | 94 | # Ignore built ts files 95 | __tests__/runner/* 96 | __tests__/changeinfo.xml 97 | src/test.ts 98 | src/*.js 99 | __tests__/CHANGELOG-heavy.md 100 | lib 101 | test/dist/ 102 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | .vscode/ 4 | tsconfig.json 5 | .prettierignore 6 | .prettierrc.json -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /test/dist/ 2 | lib/ 3 | node_modules/ 4 | README.md 5 | *.html -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "workbench.colorCustomizations": { 5 | "statusBar.background": "#000000", 6 | "statusBar.foreground": "#e7e7e7", 7 | "statusBarItem.hoverBackground": "#1a1a1a" 8 | }, 9 | "peacock.color": "#000000", 10 | "peacock.affectActivityBar": false, 11 | "peacock.affectTitleBar": false, 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Principal Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/dw/@principalstudio/html-webpack-inject-preload)](https://www.npmjs.com/package/@principalstudio/html-webpack-inject-preload) [![node-current](https://img.shields.io/node/v/@principalstudio/html-webpack-inject-preload)](https://nodejs.org/) 2 | 3 | 4 | 5 | # HTML Webpack Inject Preload 6 | A [HTML Webpack Plugin](https://github.com/jantimon/html-webpack-plugin) for injecting [<link rel='preload'>](https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content) 7 | 8 | This plugin allows to add preload links anywhere you want. 9 | 10 | # Installation 11 | 12 | You need to have HTMLWebpackPlugin v4 or v5 to make this plugin work. 13 | 14 | ``` 15 | npm i -D @principalstudio/html-webpack-inject-preload 16 | ``` 17 | 18 | **webpack.config.js** 19 | 20 | ```js 21 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 22 | const HtmlWebpackInjectPreload = require('@principalstudio/html-webpack-inject-preload'); 23 | 24 | module.exports = { 25 | entry: 'index.js', 26 | output: { 27 | path: __dirname + '/dist', 28 | filename: 'index_bundle.js' 29 | }, 30 | plugins: [ 31 | new HtmlWebpackPlugin(), 32 | new HtmlWebpackInjectPreload({ 33 | files: [ 34 | { 35 | match: /.*\.woff2$/, 36 | attributes: {as: 'font', type: 'font/woff2', crossorigin: true }, 37 | }, 38 | { 39 | match: /vendors\.[a-z-0-9]*.css$/, 40 | attributes: {as: 'style' }, 41 | }, 42 | ] 43 | }) 44 | ] 45 | } 46 | ``` 47 | 48 | **Options** 49 | 50 | * files: An array of files object 51 | * match: A regular expression to target files you want to preload 52 | * attributes: Any attributes you want to use. The plugin will add the attribute `rel="preload"` by default. 53 | 54 | **Usage** 55 | 56 | The plugin is really simple to use. The plugin injects in `headTags`, before any link, the preload elements. 57 | 58 | For example 59 | 60 | ```html 61 | 62 | 63 | 64 | 65 | Webpack App 66 | <%= htmlWebpackPlugin.tags.headTags %> 67 | 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | will generate 75 | 76 | ```html 77 | 78 | 79 | 80 | 81 | Webpack App 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ``` 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@principalstudio/html-webpack-inject-preload", 3 | "version": "1.2.7", 4 | "description": "A HTML Webpack plugin for injecting ", 5 | "main": "lib/main.js", 6 | "types": "lib/main.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "jest --config=./test/jest.config.js", 10 | "all": "npm run build && npm run test && npm link", 11 | "all-quick": "npm run build && npm link", 12 | "prepublishOnly": "npm run build && npm run test" 13 | }, 14 | "engines": { 15 | "node": ">=10.23" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/principalstudio/html-webpack-inject-preload.git" 20 | }, 21 | "keywords": [ 22 | "webpack", 23 | "plugin", 24 | "html-webpack-plugin", 25 | "preload", 26 | "inject" 27 | ], 28 | "author": "Principal Studio", 29 | "bugs": { 30 | "url": "https://github.com/principalstudio/html-webpack-inject-preload/issues" 31 | }, 32 | "homepage": "https://github.com/principalstudio/html-webpack-inject-preload#readme", 33 | "devDependencies": { 34 | "@types/jest": "^26.0.20", 35 | "@types/node": "^14.14.32", 36 | "@typescript-eslint/parser": "^4.16.1", 37 | "css-loader": "^5.1.1", 38 | "eslint": "^7.21.0", 39 | "file-loader": "^6.2.0", 40 | "html-webpack-plugin": "5.3.0", 41 | "jest": "^26.6.3", 42 | "mini-css-extract-plugin": "^1.3.9", 43 | "prettier": "^2.2.1", 44 | "ts-jest": "^26.5.3", 45 | "typescript": "^4.2.3", 46 | "url-loader": "^4.1.1", 47 | "webpack": "^5.24.4" 48 | }, 49 | "peerDependencies": { 50 | "html-webpack-plugin": "^4.0.0 || ^5.0.0", 51 | "webpack": "^4.0.0 || ^5.0.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | default as HtmlWebpackPluginInstance, 3 | HtmlTagObject, 4 | } from 'html-webpack-plugin'; 5 | import type {Compilation, Compiler, WebpackPluginInstance} from 'webpack'; 6 | 7 | declare namespace HtmlWebpackInjectPreload { 8 | interface Options { 9 | files: HtmlWebpackInjectPreload.File[]; 10 | } 11 | 12 | interface File { 13 | match: RegExp; 14 | attributes: Record; 15 | } 16 | } 17 | 18 | interface HtmlWebpackPluginData { 19 | headTags: HtmlWebpackPluginInstance.HtmlTagObject[]; 20 | bodyTags: HtmlWebpackPluginInstance.HtmlTagObject[]; 21 | outputName: string; 22 | publicPath: string; 23 | plugin: HtmlWebpackPluginInstance; 24 | } 25 | 26 | /** 27 | * Inject preload files before the content of the targeted files 28 | * 29 | * @example 30 | * new HtmlWebpackInjectPreload({ 31 | * files: [ 32 | * { 33 | * match: /.*\.woff2/$, 34 | * attributes: { rel: 'preload', as: 'font', type: 'font/woff2', 35 | * crossorigin: true }, 36 | * }, 37 | * { 38 | * match: /vendors\.[a-z-0-9]*.css/$, 39 | * attributes: { rel: 'preload', as: 'style' }, 40 | * }, 41 | * ], 42 | * }) 43 | * 44 | * @class InjectPreloadFiles 45 | */ 46 | class HtmlWebpackInjectPreload implements WebpackPluginInstance { 47 | private options: HtmlWebpackInjectPreload.Options = { 48 | files: [], 49 | }; 50 | 51 | /** 52 | * Creates an instance of HtmlWebpackInjectPreload. 53 | * 54 | * @memberof InjectPreloadFiles 55 | */ 56 | constructor(options: HtmlWebpackInjectPreload.Options) { 57 | this.options = Object.assign(this.options, options); 58 | } 59 | 60 | /** 61 | * Extract HTMLWebpack Plugin by jahed 62 | * 63 | * @param compiler 64 | */ 65 | private extractHtmlWebpackPluginModule = ( 66 | compiler: Compiler, 67 | ): typeof HtmlWebpackPluginInstance | null => { 68 | const htmlWebpackPlugin = (compiler.options.plugins || []).find(plugin => { 69 | return plugin.constructor.name === 'HtmlWebpackPlugin'; 70 | }) as typeof HtmlWebpackPluginInstance | undefined; 71 | if (!htmlWebpackPlugin) { 72 | return null; 73 | } 74 | const HtmlWebpackPlugin = htmlWebpackPlugin.constructor; 75 | if (!HtmlWebpackPlugin || !('getHooks' in HtmlWebpackPlugin)) { 76 | return null; 77 | } 78 | return HtmlWebpackPlugin as typeof HtmlWebpackPluginInstance; 79 | }; 80 | 81 | private addLinks( 82 | compilation: Compilation, 83 | htmlPluginData: HtmlWebpackPluginData, 84 | ) { 85 | //Get public path 86 | //html-webpack-plugin v5 87 | let publicPath = htmlPluginData.publicPath; 88 | 89 | //html-webpack-plugin v4 90 | if (typeof publicPath === 'undefined') { 91 | if ( 92 | htmlPluginData.plugin.options?.publicPath && 93 | htmlPluginData.plugin.options?.publicPath !== 'auto' 94 | ) { 95 | publicPath = htmlPluginData.plugin.options?.publicPath; 96 | } else { 97 | publicPath = 98 | typeof compilation.options.output.publicPath === 'string' 99 | ? compilation.options.output.publicPath 100 | : '/'; 101 | } 102 | 103 | //prevent wrong url 104 | if (publicPath[publicPath.length - 1] !== '/') { 105 | publicPath = publicPath + '/'; 106 | } 107 | } 108 | 109 | //Get assets name 110 | const assets = new Set(Object.keys(compilation.assets)); 111 | compilation.chunks.forEach(chunk => { 112 | chunk.files.forEach((file: string) => assets.add(file)); 113 | }); 114 | 115 | //Find first link index to inject before 116 | const linkIndex = htmlPluginData.headTags.findIndex( 117 | tag => tag.tagName === 'link', 118 | ); 119 | 120 | assets.forEach(asset => { 121 | for (let index = 0; index < this.options.files.length; index++) { 122 | const file = this.options.files[index]; 123 | 124 | if (file.match.test(asset)) { 125 | let href = 126 | file.attributes && file.attributes.href 127 | ? file.attributes.href 128 | : false; 129 | if (href === false || typeof href === 'undefined') { 130 | href = asset; 131 | } 132 | href = href[0] === '/' ? href : publicPath + href; 133 | 134 | const preload: HtmlTagObject = { 135 | tagName: 'link', 136 | attributes: Object.assign( 137 | { 138 | rel: 'preload', 139 | href, 140 | }, 141 | file.attributes, 142 | ), 143 | voidTag: true, 144 | meta: { 145 | plugin: 'html-webpack-inject-preload', 146 | }, 147 | }; 148 | 149 | if (linkIndex > -1) { 150 | //before link 151 | htmlPluginData.headTags.splice(linkIndex, 0, preload); 152 | } else { 153 | // before everything 154 | htmlPluginData.headTags.unshift(preload); 155 | } 156 | } 157 | } 158 | }); 159 | 160 | return htmlPluginData; 161 | } 162 | 163 | apply(compiler: Compiler) { 164 | compiler.hooks.compilation.tap('HtmlWebpackInjectPreload', compilation => { 165 | const HtmlWebpackPlugin = this.extractHtmlWebpackPluginModule(compiler); 166 | if (!HtmlWebpackPlugin) { 167 | throw new Error( 168 | 'HtmlWebpackInjectPreload needs to be used with html-webpack-plugin 4 or 5', 169 | ); 170 | } 171 | 172 | const hooks = HtmlWebpackPlugin.getHooks(compilation); 173 | hooks.alterAssetTagGroups.tapAsync( 174 | 'HtmlWebpackInjectPreload', 175 | (htmlPluginData, callback: any) => { 176 | try { 177 | callback(null, this.addLinks(compilation, htmlPluginData)); 178 | } catch (error) { 179 | callback(error); 180 | } 181 | }, 182 | ); 183 | }); 184 | } 185 | } 186 | 187 | export = HtmlWebpackInjectPreload; 188 | -------------------------------------------------------------------------------- /test/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/principalstudio/html-webpack-inject-preload/647ebcbaaf7ab7f319218a261d347056b2d451f8/test/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /test/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/principalstudio/html-webpack-inject-preload/647ebcbaaf7ab7f319218a261d347056b2d451f8/test/Roboto-Regular.woff -------------------------------------------------------------------------------- /test/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/principalstudio/html-webpack-inject-preload/647ebcbaaf7ab7f319218a261d347056b2d451f8/test/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /test/entry.js: -------------------------------------------------------------------------------- 1 | require('./style.css'); -------------------------------------------------------------------------------- /test/expected-1.html: -------------------------------------------------------------------------------- 1 | Webpack App -------------------------------------------------------------------------------- /test/expected-2.html: -------------------------------------------------------------------------------- 1 | Webpack App -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import HtmlWebpackInjectPreload from '../src/main'; 2 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 4 | import webpack, {WebpackPluginInstance} from 'webpack'; 5 | import path from 'path'; 6 | import fs from 'fs'; 7 | 8 | const options: HtmlWebpackInjectPreload.Options = { 9 | files: [ 10 | { 11 | match: /.*\.woff2$/, 12 | attributes: { 13 | as: 'font', 14 | type: 'font/woff2', 15 | crossorigin: true, 16 | }, 17 | }, 18 | { 19 | match: /.*\.woff$/, 20 | attributes: { 21 | as: 'font', 22 | type: 'font/woff', 23 | crossorigin: true, 24 | }, 25 | }, 26 | { 27 | match: /.*\.css$/, 28 | attributes: {as: 'style', href: 'test-alt.css'}, 29 | }, 30 | { 31 | match: /.*\.null/, 32 | attributes: {href: false}, 33 | }, 34 | ], 35 | }; 36 | 37 | const config: webpack.Configuration = { 38 | mode: 'production', 39 | context: path.resolve(__dirname), 40 | entry: path.join(__dirname, 'entry.js'), 41 | module: { 42 | rules: [ 43 | { 44 | test: /\.css$/i, 45 | use: [MiniCssExtractPlugin.loader, 'css-loader'], 46 | }, 47 | { 48 | test: /\.(woff|woff2)$/, 49 | use: [ 50 | { 51 | loader: 'url-loader', 52 | options: { 53 | name: '[name].[ext]', 54 | limit: 8192, 55 | }, 56 | }, 57 | ], 58 | }, 59 | ], 60 | }, 61 | output: { 62 | path: path.join(__dirname, 'dist/test1'), 63 | publicPath: '/', 64 | }, 65 | plugins: [ 66 | new MiniCssExtractPlugin() as WebpackPluginInstance, 67 | new HtmlWebpackPlugin(), 68 | new HtmlWebpackInjectPreload(options), 69 | ], 70 | }; 71 | 72 | describe('HTMLWebpackInjectPreload', () => { 73 | it('test plugin', done => { 74 | const compiler = webpack(config); 75 | 76 | compiler.run((err, stats) => { 77 | if (err) expect(err).toBeNull(); 78 | 79 | const statsErrors = stats ? stats.compilation.errors : []; 80 | if (statsErrors.length > 0) { 81 | console.error(statsErrors); 82 | } 83 | expect(statsErrors.length).toBe(0); 84 | 85 | const result = fs.readFileSync( 86 | path.join(__dirname, 'dist/test1/index.html'), 87 | 'utf8', 88 | ); 89 | const expected = fs.readFileSync( 90 | path.join(__dirname, 'expected-1.html'), 91 | 'utf8', 92 | ); 93 | expect(result).toBe(expected); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('test plugin public path', done => { 99 | const config2 = Object.assign(config, { 100 | output: { 101 | path: path.join(__dirname, 'dist/test2'), 102 | publicPath: '/test', 103 | }, 104 | }); 105 | const compiler = webpack(config2); 106 | 107 | compiler.run((err, stats) => { 108 | if (err) expect(err).toBeNull(); 109 | 110 | const statsErrors = stats ? stats.compilation.errors : []; 111 | if (statsErrors.length > 0) { 112 | console.error(statsErrors); 113 | } 114 | expect(statsErrors.length).toBe(0); 115 | 116 | const result = fs.readFileSync( 117 | path.join(__dirname, 'dist/test2/index.html'), 118 | 'utf8', 119 | ); 120 | const expected = fs.readFileSync( 121 | path.join(__dirname, 'expected-2.html'), 122 | 'utf8', 123 | ); 124 | expect(result).toBe(expected); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ["**/?(*.)+(spec|test).ts?(x)"], 5 | setupFilesAfterEnv: ['/setupTest.js'], 6 | }; -------------------------------------------------------------------------------- /test/setupTest.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(25000); 2 | const fs = require('fs'); 3 | if (!fs.existsSync('./test/dist')) 4 | fs.mkdirSync('./test/dist'); -------------------------------------------------------------------------------- /test/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-weight: 400; 4 | src: url('./Roboto-Regular.woff2') format('woff2'), 5 | url('./Roboto-Regular.woff') format('woff'); 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | font-weight: 700; 11 | src: url('./Roboto-Bold.woff2') format('woff2'); 12 | } 13 | 14 | body { 15 | font-family: Roboto, sans-serif; 16 | background-color: red; 17 | color: white; 18 | } 19 | 20 | body::before { 21 | content: 'Hello world' 22 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 5 | "module": "commonjs", 6 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "outDir": "./lib", 8 | "moduleResolution": "node", 9 | /* Redirect output structure to the directory. */ 10 | "rootDir": "./src", 11 | /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 12 | "strict": true, 13 | /* Enable all strict type-checking options. */ 14 | "noImplicitAny": false, 15 | /* Raise error on expressions and declarations with an implied 'any' type. */ 16 | "esModuleInterop": true, 17 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 18 | "lib": [ 19 | "es2019" 20 | ], 21 | "declaration": true, 22 | }, 23 | "exclude": ["node_modules", "**/*.test.ts", "lib"], 24 | } --------------------------------------------------------------------------------