diff --git a/.gitignore b/.gitignore
index 3e523f5..643981d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,3 +108,6 @@ dist
package-lock.json
pnpm-lock.yaml
yarn.lock
+
+/index.cjs
+/index.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e582e8a..b5114eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+## 0.5.3 (2022-10-16)
+
+- ee0a882 `src-output` -> `__snapshots__`
+- 16593ff v0.5.3
+- 2a5752b docs: v0.5.3
+- 236730a refactor: use vite-plugin-utils
+- bb793a5 chore: update config
+- 1148671 feat: support `"type": "module"`
+- f590da1 chore: `"strict": true`
+- cf98458 bump deps
+- 04f95f7 chore: bump deps
+- 0434172 chore: update comments
+
+---
## [2022-05-04] v0.3.0
diff --git a/README.md b/README.md
index 5c3e358..0f7992e 100644
--- a/README.md
+++ b/README.md
@@ -26,11 +26,10 @@ export default {
}
```
-## API
+## API (Define)
```ts
export interface Options {
- extensions?: string[]
filter?: (id: string) => false | undefined
dynamic?: {
/**
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 332715a..c2a6c70 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -26,11 +26,10 @@ export default {
}
```
-## API
+## API (Define)
```ts
export interface Options {
- extensions?: string[]
filter?: (id: string) => false | undefined
dynamic?: {
/**
diff --git a/package.json b/package.json
index e6c8259..b056922 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,16 @@
{
"name": "vite-plugin-commonjs",
- "version": "0.5.2",
+ "version": "0.5.3",
"description": "A pure JavaScript implementation of CommonJs",
- "main": "dist/index.js",
+ "type": "module",
+ "main": "index.js",
+ "types": "src",
+ "exports": {
+ ".": {
+ "import": "./index.js",
+ "require": "./index.cjs"
+ }
+ },
"repository": {
"type": "git",
"url": "git+https://github.com/vite-plugin/vite-plugin-commonjs.git"
@@ -10,18 +18,20 @@
"author": "草鞋没号 <308487730@qq.com>",
"license": "MIT",
"scripts": {
- "build": "rm -rf dist && tsc",
- "prepublishOnly": "npm run build",
- "test": "vite -c test/vite.config.ts"
+ "dev": "vite build --watch",
+ "build": "vite build",
+ "test": "vite -c test/vite.config.ts",
+ "prepublishOnly": "npm run build"
},
"dependencies": {
- "fast-glob": "^3.2.11",
- "vite-plugin-dynamic-import": "^0.9.9"
+ "fast-glob": "~3.2.11"
},
"devDependencies": {
- "@types/node": "^17.0.36",
- "typescript": "^4.7.2",
- "vite": "^3.0.0-alpha.11"
+ "@types/node": "^18.7.14",
+ "typescript": "^4.7.4",
+ "vite": "^3.2.0-beta.2",
+ "vite-plugin-dynamic-import": "^1.2.3",
+ "vite-plugin-utils": "^0.3.3"
},
"keywords": [
"vite",
@@ -30,6 +40,8 @@
"require"
],
"files": [
- "dist"
+ "src",
+ "index.cjs",
+ "index.js"
]
}
diff --git a/src/analyze.ts b/src/analyze.ts
index 3a469f1..ce85913 100644
--- a/src/analyze.ts
+++ b/src/analyze.ts
@@ -1,5 +1,5 @@
-import { AcornNode } from './types'
-import { simpleWalk } from './utils'
+import type { AcornNode } from './types'
+import { walk } from 'vite-plugin-utils/function'
// ①(🎯): Top-level scope statement types, it also means statements that can be converted
// 顶级作用于语句类型,这种可以被无缝换成 import
@@ -56,7 +56,7 @@ export function analyzer(ast: AcornNode, code: string, id: string): Analyzed {
exports: [],
}
- simpleWalk(ast, {
+ walk.sync(ast, {
CallExpression(node, ancestors) {
if (node.callee.name !== 'require') return
@@ -71,7 +71,7 @@ export function analyzer(ast: AcornNode, code: string, id: string): Analyzed {
dynamic: checkDynamicId(node),
})
},
- AssignmentExpression(node, ancestors) {
+ AssignmentExpression(node) {
if (node.left.type !== 'MemberExpression') return
if (!(node.left.object.type === 'Identifier' && ['module', 'exports'].includes(node.left.object.name))) return
@@ -109,7 +109,7 @@ function checkDynamicId(node: AcornNode): RequireStatement['dynamic'] {
//
// Will be return nearset scope ancestor node (🎯-①)
// 这将返回最近作用域的祖先节点
-function findTopLevelScope(ancestors: AcornNode[]): AcornNode {
+function findTopLevelScope(ancestors: AcornNode[]): AcornNode | undefined {
const ances = ancestors.map(an => an.type).join()
const arr = [...ancestors].reverse()
diff --git a/src/dynamic-require.ts b/src/dynamic-require.ts
index 50c18b3..7ff6c58 100644
--- a/src/dynamic-require.ts
+++ b/src/dynamic-require.ts
@@ -1,22 +1,18 @@
-import path from 'path'
+import path from 'node:path'
import type { ResolvedConfig } from 'vite'
+import fastGlob from 'fast-glob'
import {
type Resolved,
- dynamicImportToGlob,
Resolve,
- utils,
+ dynamicImportToGlob,
+ mappingPath,
+ toLooseGlob,
} from 'vite-plugin-dynamic-import'
-import fastGlob from 'fast-glob'
+import { normalizePath, relativeify } from 'vite-plugin-utils/function'
import type { Options } from '.'
import type { Analyzed } from './analyze'
-import { AcornNode } from './types'
-
-const {
- normallyImporteeRE,
- tryFixGlobSlash,
- toDepthGlob,
- mappingPath,
-} = utils
+import type { AcornNode } from './types'
+import { normallyImporteeRE } from './utils'
export interface DynamicRequireRecord {
node: AcornNode
@@ -33,7 +29,7 @@ export class DynaimcRequire {
constructor(
private config: ResolvedConfig,
- private options: Options,
+ private options: Options & { extensions: string[] },
private resolve = new Resolve(config),
) { }
@@ -53,7 +49,7 @@ export class DynaimcRequire {
analyzed.code,
analyzed.id,
this.resolve,
- options.extensions!,
+ this.options.extensions,
options.dynamic?.loose !== false,
)
if (!globResult) continue
@@ -61,7 +57,7 @@ export class DynaimcRequire {
let { files, resolved, normally } = globResult
// skip itself
- files = files.filter(f => path.join(path.dirname(id), f) !== id)
+ files = files!.filter(f => normalizePath(path.join(path.dirname(id), f)) !== id)
// execute the dynamic.onFiles
options.dynamic?.onFiles && (files = options.dynamic?.onFiles(files, id) || files)
@@ -72,7 +68,10 @@ export class DynaimcRequire {
if (!files?.length) continue
- const maps = mappingPath(files, resolved)
+ const maps = mappingPath(
+ files,
+ resolved ? { [resolved.alias.relative]: resolved.alias.findString } : undefined,
+ )
let counter2 = 0
record.dynaimc = {
importee: [],
@@ -124,15 +123,15 @@ async function globFiles(
resolved?: Resolved
/** After `expressiontoglob()` processing, it may become a normally path */
normally?: string
-}> {
+} | undefined> {
let files: string[]
- let resolved: Resolved
+ let resolved: Resolved | undefined
let normally: string
const PAHT_FILL = '####/'
const EXT_FILL = '.extension'
- let glob: string
- let globRaw: string
+ let glob: string | null
+ let globRaw!: string
glob = await dynamicImportToGlob(
node.arguments[0],
@@ -162,21 +161,25 @@ async function globFiles(
return
}
- glob = tryFixGlobSlash(glob)
- loose !== false && (glob = toDepthGlob(glob))
- glob.includes(PAHT_FILL) && (glob = glob.replace(PAHT_FILL, ''))
- glob.endsWith(EXT_FILL) && (glob = glob.replace(EXT_FILL, ''))
-
- const fileGlob = path.extname(glob)
- ? glob
- // If not ext is not specified, fill necessary extensions
- // e.g.
- // `./foo/*` -> `./foo/*.{js,ts,vue,...}`
- : glob + `.{${extensions.map(e => e.replace(/^\./, '')).join(',')}}`
+ // @ts-ignore
+ const globs = [].concat(loose ? toLooseGlob(glob) : glob)
+ .map((g: any) => {
+ g.includes(PAHT_FILL) && (g = g.replace(PAHT_FILL, ''))
+ g.endsWith(EXT_FILL) && (g = g.replace(EXT_FILL, ''))
+ return g
+ })
+ const fileGlobs = globs
+ .map(g => path.extname(g)
+ ? g
+ // If not ext is not specified, fill necessary extensions
+ // e.g.
+ // `./foo/*` -> `./foo/*.{js,ts,vue,...}`
+ : g + `.{${extensions.map(e => e.replace(/^\./, '')).join(',')}}`
+ )
files = fastGlob
- .sync(fileGlob, { cwd: /* 🚧-① */path.dirname(importer) })
- .map(file => !file.startsWith('.') ? /* 🚧-② */`./${file}` : file)
+ .sync(fileGlobs, { cwd: /* 🚧-① */path.dirname(importer) })
+ .map(file => relativeify(file))
return { files, resolved }
}
diff --git a/src/generate-export.ts b/src/generate-export.ts
index d947a92..bf6f4c1 100644
--- a/src/generate-export.ts
+++ b/src/generate-export.ts
@@ -2,7 +2,7 @@ import { Analyzed } from './analyze'
export interface ExportsRuntime {
polyfill: string
- exportDeclaration: string
+ exportDeclaration: string
}
export function generateExport(analyzed: Analyzed): ExportsRuntime | null {
@@ -11,8 +11,8 @@ export function generateExport(analyzed: Analyzed): ExportsRuntime | null {
}
const memberDefault = analyzed.exports
- // Find `module.exports` or `exports.default`
- .find(exp => exp.token.left === 'module' || exp.token.right === 'default')
+ // Find `module.exports` or `exports.default`
+ .find(exp => exp.token.left === 'module' || exp.token.right === 'default')
let members = analyzed.exports
// Exclude `module.exports` and `exports.default`
@@ -20,7 +20,7 @@ export function generateExport(analyzed: Analyzed): ExportsRuntime | null {
.map(exp => exp.token.right)
// Remove duplicate export
members = [...new Set(members)]
-
+
const membersDeclaration = members.map(
m => `const __CJS__export_${m}__ = (module.exports == null ? {} : module.exports).${m}`,
)
diff --git a/src/generate-import.ts b/src/generate-import.ts
index 0ec9860..afb0378 100644
--- a/src/generate-import.ts
+++ b/src/generate-import.ts
@@ -48,14 +48,14 @@ export function generateImport(analyzed: Analyzed) {
topScopeNode,
dynamic,
} = req
-
+
// ③(🚧)
// Processed in dynamic-require.ts
if (dynamic === 'dynamic') continue
const impt: ImportRecord = { node, topScopeNode }
const importName = `__CJS__import__${count++}__`
-
+
const requireIdNode = node.arguments[0]
let requireId: string
if (!requireIdNode) continue // Not value - require()
@@ -65,13 +65,13 @@ export function generateImport(analyzed: Analyzed) {
requireId = requireIdNode.quasis[0].value.raw
}
- if (!requireId) {
+ if (!requireId!) {
const codeSnippets = analyzed.code.slice(node.start, node.end)
throw new Error(`The following require statement cannot be converted.
-> ${codeSnippets}
${'^'.repeat(codeSnippets.length)}`)
}
-
+
if (topScopeNode) {
// ①(🎯)
@@ -113,7 +113,7 @@ export function generateImport(analyzed: Analyzed) {
impt.importee = `import { ${LV_str('as')} } from '${requireId}'`
}
} else if (init.type === 'MemberExpression') {
- const onlyOneMember = ancestors.find(an => an.type === 'MemberExpression').property.name
+ const onlyOneMember = ancestors.find(an => an.type === 'MemberExpression')?.property.name
const importDefault = onlyOneMember === 'default'
if (typeof LV === 'string') {
if (importDefault) {
diff --git a/src/index.ts b/src/index.ts
index 85131eb..be90140 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,21 +1,19 @@
-import path from 'path'
+import path from 'node:path'
import type { Plugin, ResolvedConfig } from 'vite'
-import { analyzer, TopScopeType } from './analyze'
-import { generateImport } from './generate-import'
-import { generateExport } from './generate-export'
import {
- cleanUrl,
- isCommonjs,
- JS_EXTENSIONS,
+ DEFAULT_EXTENSIONS,
+ KNOWN_SFC_EXTENSIONS,
KNOWN_ASSET_TYPES,
KNOWN_CSS_TYPES,
- KNOWN_SFC_EXTENSIONS,
- MagicString,
-} from './utils'
+} from 'vite-plugin-utils/constant'
+import { MagicString } from 'vite-plugin-utils/function'
+import { analyzer, TopScopeType } from './analyze'
+import { generateImport } from './generate-import'
+import { generateExport } from './generate-export'
+import { isCommonjs } from './utils'
import { DynaimcRequire } from './dynamic-require'
export interface Options {
- extensions?: string[]
filter?: (id: string) => false | undefined
dynamic?: {
/**
@@ -41,10 +39,7 @@ export interface Options {
export default function commonjs(options: Options = {}): Plugin {
let config: ResolvedConfig
- const extensions = JS_EXTENSIONS
- .concat(KNOWN_SFC_EXTENSIONS)
- .concat(KNOWN_ASSET_TYPES)
- .concat(KNOWN_CSS_TYPES)
+ let extensions = DEFAULT_EXTENSIONS
let dynaimcRequire: DynaimcRequire
return {
@@ -52,18 +47,23 @@ export default function commonjs(options: Options = {}): Plugin {
name: 'vite-plugin-commonjs',
configResolved(_config) {
config = _config
- options.extensions = [...new Set((config.resolve?.extensions || extensions).concat(options.extensions || []))]
- dynaimcRequire = new DynaimcRequire(_config, options)
+ // https://github.com/vitejs/vite/blob/37ac91e5f680aea56ce5ca15ce1291adc3cbe05e/packages/vite/src/node/plugins/resolve.ts#L450
+ if (config.resolve?.extensions) extensions = config.resolve.extensions
+ dynaimcRequire = new DynaimcRequire(_config, {
+ ...options,
+ extensions: [
+ ...extensions,
+ ...KNOWN_SFC_EXTENSIONS,
+ ...KNOWN_ASSET_TYPES.map(type => '.' + type),
+ ...KNOWN_CSS_TYPES.map(type => '.' + type),
+ ],
+ })
},
async transform(code, id) {
- const pureId = cleanUrl(id)
- const extensions = JS_EXTENSIONS.concat(KNOWN_SFC_EXTENSIONS)
- const { ext } = path.parse(pureId)
-
- if (/node_modules\/(?!\.vite\/)/.test(pureId)) return
- if (!extensions.includes(ext)) return
+ if (/node_modules\/(?!\.vite\/)/.test(id)) return
+ if (!extensions.includes(path.extname(id))) return
if (!isCommonjs(code)) return
- if (options.filter?.(pureId) === false) return
+ if (options.filter?.(id) === false) return
const ast = this.parse(code)
const analyzed = analyzer(ast, code, id)
@@ -74,7 +74,7 @@ export default function commonjs(options: Options = {}): Plugin {
: generateExport(analyzed)
const dynamics = await dynaimcRequire.generateRuntime(analyzed)
- const promotionImports = []
+ const hoistImports = []
const ms = new MagicString(code)
// require
@@ -88,7 +88,7 @@ export default function commonjs(options: Options = {}): Plugin {
} = impt
const importee = imptee + ';'
- let importStatement: string
+ let importStatement: string | undefined
if (topScopeNode) {
if (topScopeNode.type === TopScopeType.ExpressionStatement) {
importStatement = importee
@@ -97,7 +97,7 @@ export default function commonjs(options: Options = {}): Plugin {
}
} else {
// TODO: Merge duplicated require id
- promotionImports.push(importee)
+ hoistImports.push(importee)
importStatement = importName
}
@@ -108,8 +108,8 @@ export default function commonjs(options: Options = {}): Plugin {
}
}
- if (promotionImports.length) {
- ms.prepend(['/* import-promotion-S */', ...promotionImports, '/* import-promotion-E */'].join(' '))
+ if (hoistImports.length) {
+ ms.prepend(['/* import-hoist-S */', ...hoistImports, '/* import-hoist-E */'].join(' '))
}
// exports
diff --git a/src/utils.ts b/src/utils.ts
index ae48182..6e8b25c 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,69 +1,12 @@
-import { builtinModules } from 'module'
-import { type AcornNode } from './types'
+import { builtinModules } from 'node:module'
+import { multilineCommentsRE, singlelineCommentsRE } from 'vite-plugin-utils/constant'
// ------------------------------------------------- RegExp
-export const multilineCommentsRE = /\/\*(.|[\r\n])*?\*\//gm
-export const singlelineCommentsRE = /\/\/.*/g
-export const queryRE = /\?.*$/s
-export const hashRE = /#.*$/s
+export const normallyImporteeRE = /^\.{1,2}\/[.-/\w]+(\.\w+)$/
// ------------------------------------------------- const
-export const JS_EXTENSIONS = [
- '.mjs',
- '.js',
- '.ts',
- '.jsx',
- '.tsx',
- '.cjs'
-]
-export const KNOWN_SFC_EXTENSIONS = [
- '.vue',
- '.svelte',
-]
-// https://github.com/vitejs/vite/blob/d6418605577319b2f92ea37081e34376bb47b286/packages/vite/src/node/constants.ts#L66
-export const KNOWN_ASSET_TYPES = [
- // images
- 'png',
- 'jpg',
- 'jpeg',
- 'gif',
- 'svg',
- 'ico',
- 'webp',
- 'avif',
-
- // media
- 'mp4',
- 'webm',
- 'ogg',
- 'mp3',
- 'wav',
- 'flac',
- 'aac',
-
- // fonts
- 'woff2?',
- 'eot',
- 'ttf',
- 'otf',
-
- // other
- 'webmanifest',
- 'pdf',
- 'txt'
-]
-export const KNOWN_CSS_TYPES = [
- 'css',
- 'less',
- 'sass',
- 'scss',
- 'styl',
- 'stylus',
- 'pcss',
- 'postcss',
-]
export const builtins = [
...builtinModules.map(m => !m.startsWith('_')),
...builtinModules.map(m => !m.startsWith('_')).map(m => `node:${m}`)
@@ -71,10 +14,6 @@ export const builtins = [
// ------------------------------------------------- function
-export function cleanUrl(url: string): string {
- return url.replace(hashRE, '').replace(queryRE, '')
-}
-
export function isCommonjs(code: string) {
// Avoid matching the content of the comment
code = code
@@ -82,71 +21,3 @@ export function isCommonjs(code: string) {
.replace(singlelineCommentsRE, '')
return /\b(?:require|module|exports)\b/.test(code)
}
-
-export function simpleWalk(
- ast: AcornNode,
- visitors: {
- [type: string]: (node: AcornNode, ancestors: AcornNode[]) => void,
- },
- ancestors: AcornNode[] = [],
-) {
- if (!ast) return
- if (Array.isArray(ast)) {
- for (const element of ast as AcornNode[]) {
- simpleWalk(element, visitors, ancestors)
- }
- } else {
- ancestors = ancestors.concat(ast)
- for (const key of Object.keys(ast)) {
- (typeof ast[key] === 'object' &&
- simpleWalk(ast[key], visitors, ancestors))
- }
- }
- visitors[ast.type]?.(ast, ancestors)
-}
-// TODO
-simpleWalk.async = function simpleWalkAsync() { }
-
-export class MagicString {
- private overwrites: { loc: [number, number]; content: string }[]
- private starts = ''
- private ends = ''
-
- constructor(
- public str: string
- ) { }
-
- public append(content: string) {
- this.ends += content
- return this
- }
-
- public prepend(content: string) {
- this.starts = content + this.starts
- return this
- }
-
- public overwrite(start: number, end: number, content: string) {
- if (end < start) {
- throw new Error(`"end" con't be less than "start".`)
- }
- if (!this.overwrites) {
- this.overwrites = []
- }
-
- this.overwrites.push({ loc: [start, end], content })
- return this
- }
-
- public toString() {
- let str = this.str
- if (this.overwrites) {
- const arr = [...this.overwrites].sort((a, b) => b.loc[0] - a.loc[0])
- for (const { loc: [start, end], content } of arr) {
- // TODO: check start or end overlap
- str = str.slice(0, start) + content + str.slice(end)
- }
- }
- return this.starts + str + this.ends
- }
-}
diff --git a/test/src-output/dynamic.tsx b/test/__snapshots__/dynamic.tsx
similarity index 100%
rename from test/src-output/dynamic.tsx
rename to test/__snapshots__/dynamic.tsx
diff --git a/test/src-output/exports.js b/test/__snapshots__/exports.js
similarity index 100%
rename from test/src-output/exports.js
rename to test/__snapshots__/exports.js
diff --git a/test/src-output/main.ts b/test/__snapshots__/main.ts
similarity index 100%
rename from test/src-output/main.ts
rename to test/__snapshots__/main.ts
diff --git a/test/src-output/module-exports/hello.cjs b/test/__snapshots__/module-exports/hello.cjs
similarity index 100%
rename from test/src-output/module-exports/hello.cjs
rename to test/__snapshots__/module-exports/hello.cjs
diff --git a/test/src-output/module-exports/world.cjs b/test/__snapshots__/module-exports/world.cjs
similarity index 100%
rename from test/src-output/module-exports/world.cjs
rename to test/__snapshots__/module-exports/world.cjs
diff --git a/test/vite.config.ts b/test/vite.config.ts
index be2b540..a9aa9de 100644
--- a/test/vite.config.ts
+++ b/test/vite.config.ts
@@ -3,7 +3,7 @@ import fs from 'fs'
import { defineConfig } from 'vite'
import commonjs from '..'
-fs.rmSync(path.join(__dirname, 'src-output'), { force: true, recursive: true })
+fs.rmSync(path.join(__dirname, '__snapshots__'), { force: true, recursive: true })
export default defineConfig({
root: __dirname,
@@ -14,7 +14,7 @@ export default defineConfig({
transform(code, id) {
if (/\/src\//.test(id)) {
// Write transformed code to output/
- const filename = id.replace('src', 'src-output')
+ const filename = id.replace('src', '__snapshots__')
const dirname = path.dirname(filename)
if (!fs.existsSync(dirname)) fs.mkdirSync(dirname)
fs.writeFileSync(filename, code)
@@ -26,5 +26,15 @@ export default defineConfig({
alias: {
'@': path.join(__dirname, 'src'),
},
+ extensions: [
+ '.cjs',
+ '.mjs',
+ '.js',
+ '.mts',
+ '.ts',
+ '.jsx',
+ '.tsx',
+ '.json',
+ ],
},
})
diff --git a/tsconfig.json b/tsconfig.json
index 9f92da1..2aa8ac1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,14 +1,15 @@
{
"compilerOptions": {
"target": "ES2019",
- "module": "CommonJS",
+ "module": "ES2022",
"esModuleInterop": true,
"moduleResolution": "Node",
"baseUrl": ".",
"declaration": true,
"outDir": "dist",
"allowSyntheticDefaultImports": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "strict": true
},
"include": ["src/**/*.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..7a389b4
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,27 @@
+import path from 'path'
+import { builtinModules } from 'module'
+import { defineConfig } from 'vite'
+import pkg from './package.json'
+
+export default defineConfig({
+ build: {
+ minify: false,
+ outDir: '',
+ emptyOutDir: false,
+ target: 'node14',
+ lib: {
+ entry: path.join(__dirname, 'src/index.ts'),
+ formats: ['es', 'cjs'],
+ fileName: format => format === 'cjs' ? '[name].cjs' : '[name].js',
+ },
+ rollupOptions: {
+ external: [
+ ...builtinModules
+ .filter(m => !m.startsWith('_'))
+ .map(m => [m, `node:${m}`])
+ .flat(),
+ ...Object.keys(pkg.dependencies),
+ ],
+ },
+ },
+})