diff --git a/.changeset/soft-experts-roll.md b/.changeset/soft-experts-roll.md index d1dbf6214880..dbbafc10f172 100644 --- a/.changeset/soft-experts-roll.md +++ b/.changeset/soft-experts-roll.md @@ -17,6 +17,7 @@ swc conversion was introduced in version 2.16.0, but the implementation still ha 7. fix some css module bugs. 8. support buildConfig.jsx: preserve . 9. support glob input in js and dts generator. +10. support banner and footer. refactor(module-tools): @@ -33,3 +34,4 @@ swc 转换是在 2.16.0 版本引入,但实现仍存在一些问题,例如 f 7. 修复一些 css module 问题。 8. 支持 buildConfig.jsx: preserve 选项。 9. 支持 glob 模式输入在 js 和 dts 生成器中。 +10. 支持 banner 和 footer 配置。 diff --git a/packages/document/module-doc/docs/en/api/config/build-config.mdx b/packages/document/module-doc/docs/en/api/config/build-config.mdx index 63ad54aae597..5f565d8e3ef6 100644 --- a/packages/document/module-doc/docs/en/api/config/build-config.mdx +++ b/packages/document/module-doc/docs/en/api/config/build-config.mdx @@ -211,6 +211,47 @@ Whether to require peerDep dependencies for external projects - **Type**: `boolean` - **Default**: `true` + +## banner + +Provides the ability to inject content into the top and bottom of each JS , CSS and DTS file. + +```ts +interface BannerAndFooter { + js?: string; + css?: string; + dts?: string; +} +``` + +- **Type**: `BannerAndFooter` +- **Default**: `{}` +- **Version**: `v2.36.0` + +Let's say you want to add copyright information to JS and CSS files. + +```ts +import { moduleTools, defineConfig } from '@edenx/module-tools'; + +const copyRight = `/* + © Copyright 2020 xxx.com or one of its affiliates. + * Some Sample Copyright Text Line + * Some Sample Copyright Text Line +*/`; + +export default defineConfig({ + plugins: [ + moduleTools(), + ], + buildConfig: { + banner: { + js: copyRight, + css: copyRight, + } + } +}); +``` + ## buildType The build type, `bundle` will package your code, `bundleless` will only do the code conversion @@ -539,6 +580,10 @@ export default defineConfig({ }); ``` +## footer + +Same as the [banner](#banner) configuration for adding a comment at the end of the output file. + ## format Used to set the output format of JavaScript files. The options `iife` and `umd` only take effect when `buildType` is `bundle`. @@ -788,6 +833,59 @@ export default { }; ``` +## resolve + +Custom module resolution options + +### resolve.mainFields + +A list of fields in package.json to try when parsing the package entry point. + +- **Type**: `string[]` +- **Default**: depends on [platform](#platform) + - node: ['module', 'main'] + - browser: ['module', 'browser', 'main'] +- **Version**: `v2.36.0` + +For example, we want to load the `js:source` field first: + +```js title="modern.config.ts" +export default defineConfig({ + buildConfig: { + resolve: { + mainFields: ['js:source', 'module', 'main'], + } + }, +}); +``` + +:::warning +`resolve.mainFields` has a lower priority than the exports field in package.json, and if an entry point is successfully resolved from exports, `resolve.mainFields` will be ignored. +::: + +### resolve.jsExtentions + +Support for implicit file extensions + +- **Type**: `string[]` +- **Default**: `['.jsx', '.tsx', '.js', '.ts', '.json']` +- **Version**: `v2.36.0` + + +Do not use implicit file extensions for css files, currently Module only supports ['.less', '.css', '.sass', '.scss'] suffixes. + +Node's parsing algorithm does not consider `.mjs` and `cjs` as implicit file extensions, so they are not included here by default, but can be included by changing this configuration: + +```js title="modern.config.ts" +export default defineConfig({ + buildConfig: { + resolve: { + jsExtentions: ['.mts', 'ts'] + } + }, +}); +``` + ## sideEffects Module sideEffects @@ -1275,6 +1373,7 @@ Path to the tsconfig file - **Type**: `string` - **Default**: `tsconfig.json` +- **Version**: `v2.36.0` ```js title="modern.config.ts" export default defineConfig({ diff --git a/packages/document/module-doc/docs/en/guide/advance/in-depth-about-build.md b/packages/document/module-doc/docs/en/guide/advance/in-depth-about-build.md index 1cd9cf9fd0b4..901c0ae232ae 100644 --- a/packages/document/module-doc/docs/en/guide/advance/in-depth-about-build.md +++ b/packages/document/module-doc/docs/en/guide/advance/in-depth-about-build.md @@ -65,7 +65,9 @@ export default defineConfig({ ## use swc -In some scenarios, esbuild is not enough to meet our needs, then we will use swc to do the code transformation, mainly in the following scenarios. +In some scenarios, esbuild is not enough to meet our needs, and we will use swc to do the code conversion. + +Starting from version **2.36.0**, the Modern.js Module will use swc by default when it comes to the following functionality, but that doesn't mean we don't use esbuild any more, the rest of the functionality will still use esbuild. - [transformImport](/api/config/build-config#transformimport) - [transformLodash](/api/config/build-config#transformlodash) @@ -74,6 +76,9 @@ In some scenarios, esbuild is not enough to meet our needs, then we will use swc - [target: es5](api/config/build-config#target) - [emitDecoratorMetadata: true](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata) +In fact, we've been using swc for full code conversion since **2.16.0**. However, swc also has some limitations, so we added [sourceType](/api/config/build-config#sourcetype) to turn off swc when the source is formatted as 'commonjs', which isn't really user-intuitive, and the cjs mode of the swc formatted outputs don't have annotate each export name, which can cause problems in node. +So we deprecated this behaviour and went back to the original design - using swc as a supplement only in situations where it was needed. + ## dts The [`buildConfig.dts`](/en/api/config/build-config#dts) configuration is mainly used for type file generation. @@ -206,3 +211,42 @@ For `js/ts` build errors, we can tell from the error message. - What is the `format` of the build process - What is the `target` of the build process - The specific error message + +## Debug mode + +From **2.36.0**, For troubleshooting purposes, the Modern.js Module provides a debug mode, which you can enable by adding the DEBUG=module environment variable when executing a build. + +```bash +DEBUG=module modern build +``` + +In debug mode, you'll see more detailed build logs output in Shell, which are mainly process logs: + +```bash +module run beforeBuildTask hooks +6ms +module run beforeBuildTask hooks done +0ms +module [DTS] Build Start +139ms +module [CJS] Build Start +1ms +``` + +In addition, Module provides the ability to debug internal workflows. You can enable more detailed debugging logging by setting the `DEBUG=module:*` environment variable. + +Currently, only `DEBUG=module:resolve` is supported, which allows you to see a detailed log of module resolution within the Module. + +```bash + module:resolve onResolve args: { + path: './src/hooks/misc.ts', + importer: '', + namespace: 'file', + resolveDir: '/Users/bytedance/modern.js/packages/solutions/module-tools', + kind: 'entry-point', + pluginData: undefined +} +0ms + module:resolve onResolve result: { + path: '/Users/bytedance/modern.js/packages/solutions/module-tools/src/hooks/misc.ts', + external: false, + namespace: 'file', + sideEffects: undefined, + suffix: '' +} +0ms +``` diff --git a/packages/document/module-doc/docs/zh/api/config/build-config.mdx b/packages/document/module-doc/docs/zh/api/config/build-config.mdx index c304c0711b32..95db06073de3 100644 --- a/packages/document/module-doc/docs/zh/api/config/build-config.mdx +++ b/packages/document/module-doc/docs/zh/api/config/build-config.mdx @@ -212,6 +212,47 @@ export default defineConfig({ - 类型: `boolean` - 默认值: `true` +## banner + +提供为每个 JS , CSS 和 DTS 文件的顶部和底部注入内容的能力。 + +```ts +interface BannerAndFooter { + js?: string; + css?: string; + dts?: string; +} +``` + +- 类型: `BannerAndFooter` +- 默认值: `{}` +- 版本: `v2.36.0` + +例如你想为 JS 和 CSS 文件添加版权信息: + +```ts +import { moduleTools, defineConfig } from '@edenx/module-tools'; + +const copyRight = `/* + © Copyright 2020 xxx.com or one of its affiliates. + * Some Sample Copyright Text Line + * Some Sample Copyright Text Line +*/`; + +export default defineConfig({ + plugins: [ + moduleTools(), + ], + buildConfig: { + banner: { + js: copyRight, + css: copyRight, + } + } +}); +``` + + ## buildType 构建类型,`bundle` 会打包你的代码,`bundleless` 只做代码的转换。 @@ -539,6 +580,10 @@ export default defineConfig({ }); ``` +## footer + +同 [banner](#banner) 配置,用于在输出文件末尾添加注释。 + ## format 用于设置 JavaScript 产物输出的格式,其中 `iife` 和 `umd` 只在 `buildType` 为 `bundle` 时生效。 @@ -789,6 +834,60 @@ export default { }; ``` +## resolve + +自定义模块解析选项 + +### resolve.mainFields + +package.json 中,在解析包的入口点时尝试的字段列表。 + +- 类型:`string[]` +- 默认值:取决于[platform](#platform) + - node: ['module', 'main'] + - browser: ['module', 'browser', 'main'] +- 版本:`v2.36.0` + +例如,我们想要先加载 `js:source` 字段: + +```js title="modern.config.ts" +export default defineConfig({ + buildConfig: { + resolve: { + mainFields: ['js:source', 'module', 'main'], + } + }, +}); +``` + +:::warning +`resolve.mainFields` 比 package.json 中 exports 字段的优先级低,如果一个入口点从 exports 成功解析,`resolve.mainFields` 将被忽略。 +::: + +### resolve.jsExtentions + +支持隐式文件扩展名 + +- 类型: `string[]` +- 默认值: `['.jsx', '.tsx', '.js', '.ts', '.json']` +- 版本:`v2.36.0` + + +对于 css 文件,请不要使用隐式文件扩展名,目前 Module 仅支持 ['.less', '.css', '.sass', '.scss'] 后缀。 + +Node 的解析算法不会将 `.mjs` 和 `cjs` 视为隐式文件扩展名,因此这里默认也不会,但是可以通过更改此配置来包含: + +```js title="modern.config.ts" +export default defineConfig({ + buildConfig: { + resolve: { + jsExtentions: ['.mts', 'ts'] + } + }, +}); +``` + + ## sideEffects 配置模块的副作用 @@ -849,6 +948,10 @@ export default defineConfig({ ## sourceType +:::warning +已废弃,此配置不会产生任何影响。 +::: + 设置源码的格式。默认情况下,会将源码作为 EsModule 进行处理。当源码使用的是 CommonJS 的时候,需要设置 `commonjs`。 - 类型:`'commonjs' | 'module'` @@ -1273,6 +1376,7 @@ TypeScript 配置文件的路径。 - 类型: `string` - 默认值: `tsconfig.json` +- 版本: `v2.36.0` ```js title="modern.config.ts" export default defineConfig({ diff --git a/packages/document/module-doc/docs/zh/guide/advance/in-depth-about-build.md b/packages/document/module-doc/docs/zh/guide/advance/in-depth-about-build.md index 4020501dd9a0..0ee05cc1a210 100644 --- a/packages/document/module-doc/docs/zh/guide/advance/in-depth-about-build.md +++ b/packages/document/module-doc/docs/zh/guide/advance/in-depth-about-build.md @@ -65,7 +65,9 @@ export default defineConfig({ ## 使用 swc -在部分场景下,esbuild 不足以满足我们的需求,此时我们会使用 swc 来做代码转换,主要有以下几个场景: +在部分场景下,esbuild 不足以满足我们的需求,此时我们会使用 swc 来做代码转换。 + +从 **2.36.0** 版本开始,涉及到以下功能时,Modern.js Module 默认会使用 swc ,但不这意味着不使用 esbuild 了,其余功能还是使用 esbuild: - [transformImport](/api/config/build-config#transformimport) - [transformLodash](/api/config/build-config#transformlodash) @@ -74,6 +76,9 @@ export default defineConfig({ - [target: es5](api/config/build-config#target) - [emitDecoratorMetadata: true](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata) +事实上,我们在 **2.16.0** 开始全量使用 swc 进行代码转换。不过 swc 同样也存在一些限制,为此我们添加了 [sourceType](/api/config/build-config#sourcetype) 配置,当源码格式为 'commonjs' 时关闭 swc, 但这种方式并不符合用户直觉,另外,swc 格式化输出的 cjs 模式没有给每个导出名称添加注释,这在 node 中使用可能会带来一些问题。 +因为我们废弃了此行为,回到了最初的设计 - 只在需要的场景下使用 swc 作为补充。 + ## 类型文件生成 [`buildConfig.dts`](/api/config/build-config#dts) 配置主要用于类型文件的生成。 @@ -202,3 +207,42 @@ bundle DTS failed: - 报错的 `format` - 报错的 `target` - 其他具体报错信息 + +## 调试模式 + +从 **2.36.0** 版本开始,为了便于排查问题,Modern.js Module 提供了调试模式,你可以在执行构建时添加 DEBUG=module 环境变量来开启调试模式。 + +```bash +DEBUG=module modern build +``` + +调试模式下,你会看到 Shell 中输出更详细的构建日志,这主要以流程日志为主: + +```bash +module run beforeBuildTask hooks +6ms +module run beforeBuildTask hooks done +0ms +module [DTS] Build Start +139ms +module [CJS] Build Start +1ms +``` + +另外,Module 还提供了调试内部工作流程的能力。你可以通过设置环境变量 `DEBUG=module:*` 来开启更详细的调试日志: + +目前只支持了 `DEBUG=module:resolve`,可以查看 Module 内部模块解析的详细日志: + +```bash + module:resolve onResolve args: { + path: './src/hooks/misc.ts', + importer: '', + namespace: 'file', + resolveDir: '/Users/bytedance/modern.js/packages/solutions/module-tools', + kind: 'entry-point', + pluginData: undefined +} +0ms + module:resolve onResolve result: { + path: '/Users/bytedance/modern.js/packages/solutions/module-tools/src/hooks/misc.ts', + external: false, + namespace: 'file', + sideEffects: undefined, + suffix: '' +} +0ms +``` diff --git a/packages/document/module-doc/docs/zh/plugins/official-list/plugin-banner.md b/packages/document/module-doc/docs/zh/plugins/official-list/plugin-banner.md index 092345c0a76f..535cf922dc95 100644 --- a/packages/document/module-doc/docs/zh/plugins/official-list/plugin-banner.md +++ b/packages/document/module-doc/docs/zh/plugins/official-list/plugin-banner.md @@ -2,6 +2,11 @@ 提供为每个 JS 和 CSS 文件的顶部和底部注入内容的能力。 +:::tip +从 `@modern-js/module-tools` v2.36.0 版本开始,该插件功能内置在 Modern.js Module 中,由 [`banner`](/api/config/build-config#banner) 和 [`footer`](/api/config/build-config#footer) +配置提供。 +::: + ## 快速开始 ### 安装 diff --git a/packages/module/plugin-module-babel/src/index.ts b/packages/module/plugin-module-babel/src/index.ts index b6be6eca6b68..56118cea2230 100644 --- a/packages/module/plugin-module-babel/src/index.ts +++ b/packages/module/plugin-module-babel/src/index.ts @@ -11,7 +11,7 @@ const name = 'babel'; export const getBabelHook = (options?: BabelTransformOptions) => ({ name, - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { compiler.hooks.transform.tapPromise({ name }, async args => { if (isJsExt(args.path) || isJsLoader(args.loader)) { const result = await require('@babel/core').transformAsync(args.code, { diff --git a/packages/module/plugin-module-banner/src/index.ts b/packages/module/plugin-module-banner/src/index.ts index d29e7ce5feed..1cf5490f06a8 100644 --- a/packages/module/plugin-module-banner/src/index.ts +++ b/packages/module/plugin-module-banner/src/index.ts @@ -1,5 +1,9 @@ import type { CliPlugin, ModuleTools } from '@modern-js/module-tools'; +/** + * @deprecated + * use config 'banner' instead. + */ export const modulePluginBanner = (options: { banner: { js?: string; css?: string }; footer?: { js?: string; css?: string }; diff --git a/packages/module/plugin-module-import/src/index.ts b/packages/module/plugin-module-import/src/index.ts index 14b7ed746244..8ae831ebfbff 100644 --- a/packages/module/plugin-module-import/src/index.ts +++ b/packages/module/plugin-module-import/src/index.ts @@ -5,7 +5,7 @@ import type { } from '@modern-js/module-tools'; /** - * deprecated named export, use config 'transformImport'. + * use config 'transformImport' instead. * @deprecated */ export const modulePluginImport = (options: { diff --git a/packages/module/plugin-module-node-polyfill/src/index.ts b/packages/module/plugin-module-node-polyfill/src/index.ts index 930d8d9ea6bc..4271b7aa0c33 100644 --- a/packages/module/plugin-module-node-polyfill/src/index.ts +++ b/packages/module/plugin-module-node-polyfill/src/index.ts @@ -3,7 +3,9 @@ import type { CliPlugin, ModuleTools, EsbuildOptions, + ICompiler, } from '@modern-js/module-tools'; +import { excludeObjectKeys, addResolveFallback } from './utils'; export interface NodePolyfillPluginOptions { // like https://github.com/Richienb/node-polyfill-webpack-plugin#excludealiases @@ -12,46 +14,6 @@ export interface NodePolyfillPluginOptions { overrides?: Partial>; } -function filterObject( - object: Record, - filter: (id: string) => boolean, -) { - const filtered: Record = {}; - Object.keys(object).forEach(key => { - if (filter(key)) { - filtered[key] = object[key]; - } - }); - return filtered; -} -function excludeObjectKeys(object: Record, keys: string[]) { - return filterObject(object, key => !keys.includes(key)); -} - -function addResolveFallback( - object: Record, - overrides: Record = {}, -) { - const keys = Object.keys(object); - const newObject: Record = {}; - for (const key of keys) { - if (object[key] === null) { - newObject[key] = path.join(__dirname, `../mock/${key}.js`); - } else { - newObject[key] = object[key] as string; - } - } - - const overridesKeys = Object.keys(overrides); - for (const key of overridesKeys) { - if (overrides[key]) { - newObject[key] = overrides[key]; - } - } - - return newObject; -} - export const modules = { assert: require.resolve('assert/'), buffer: require.resolve('buffer/'), @@ -95,48 +57,56 @@ export const modules = { zlib: require.resolve('browserify-zlib'), }; +export const getNodePolyfillHook = ( + polyfillOption: NodePolyfillPluginOptions = {}, +) => { + const polyfillModules = { + ...excludeObjectKeys( + addResolveFallback(modules, polyfillOption.overrides), + polyfillOption.excludes ?? [], + ), + }; + const polyfillModulesKeys = Object.keys(polyfillModules); + return { + name: 'node-polyfill', + apply(compiler: ICompiler) { + const plugins: NonNullable['plugins']> = [ + { + name: 'example', + setup(build) { + build.onResolve({ filter: /.*/ }, args => { + if (polyfillModulesKeys.includes(args.path)) { + return { + path: polyfillModules[args.path], + }; + } + return undefined; + }); + }, + }, + ]; + const lastBuildOptions = compiler.buildOptions; + compiler.buildOptions = { + ...lastBuildOptions, + inject: [ + ...(lastBuildOptions.inject ?? []), + path.join(__dirname, 'globals.js'), + ], + plugins: [plugins[0], ...(lastBuildOptions.plugins ?? [])], + }; + }, + }; +}; + export const modulePluginNodePolyfill = ( polyfillOption: NodePolyfillPluginOptions = {}, ): CliPlugin => ({ - name: 'polyfill-plugin', + name: '@modern-js/plugin-module-node-polyfill', setup() { - const polyfillModules = { - ...excludeObjectKeys( - addResolveFallback(modules, polyfillOption.overrides), - polyfillOption.excludes ?? [], - ), - }; - const polyfillModulesKeys = Object.keys(polyfillModules); - return { beforeBuildTask(config) { - const lastEsbuildOptions = config.esbuildOptions; - const plugins: NonNullable['plugins']> = [ - { - name: 'example', - setup(build) { - build.onResolve({ filter: /.*/ }, args => { - if (polyfillModulesKeys.includes(args.path)) { - return { - path: polyfillModules[args.path], - }; - } - return undefined; - }); - }, - }, - ]; - config.esbuildOptions = c => { - const lastEsbuildConfig = lastEsbuildOptions(c); - return { - ...lastEsbuildConfig, - inject: [ - ...(lastEsbuildConfig.inject ?? []), - path.join(__dirname, 'globals.js'), - ], - plugins: [plugins[0], ...(lastEsbuildConfig.plugins ?? [])], - }; - }; + const hook = getNodePolyfillHook(polyfillOption); + config.hooks.push(hook); return config; }, }; diff --git a/packages/module/plugin-module-node-polyfill/src/utils.ts b/packages/module/plugin-module-node-polyfill/src/utils.ts new file mode 100644 index 000000000000..274e6f865cf4 --- /dev/null +++ b/packages/module/plugin-module-node-polyfill/src/utils.ts @@ -0,0 +1,44 @@ +import path from 'path'; + +function filterObject( + object: Record, + filter: (id: string) => boolean, +) { + const filtered: Record = {}; + Object.keys(object).forEach(key => { + if (filter(key)) { + filtered[key] = object[key]; + } + }); + return filtered; +} +export function excludeObjectKeys( + object: Record, + keys: string[], +) { + return filterObject(object, key => !keys.includes(key)); +} + +export function addResolveFallback( + object: Record, + overrides: Record = {}, +) { + const keys = Object.keys(object); + const newObject: Record = {}; + for (const key of keys) { + if (object[key] === null) { + newObject[key] = path.join(__dirname, `./mock/${key}.js`); + } else { + newObject[key] = object[key] as string; + } + } + + const overridesKeys = Object.keys(overrides); + for (const key of overridesKeys) { + if (overrides[key]) { + newObject[key] = overrides[key]; + } + } + + return newObject; +} diff --git a/packages/module/plugin-module-polyfill/src/index.ts b/packages/module/plugin-module-polyfill/src/index.ts index 5b56369535e4..394a79d01bf0 100644 --- a/packages/module/plugin-module-polyfill/src/index.ts +++ b/packages/module/plugin-module-polyfill/src/index.ts @@ -1,28 +1,34 @@ import type { CliPlugin, ModuleTools } from '@modern-js/module-tools'; import { getBabelHook } from '@modern-js/plugin-module-babel'; -export const modulePluginPolyfill = (options?: { +export type Options = { targets?: Record | string; -}): CliPlugin => ({ +}; + +export const getPolyfillHook = (options?: Options) => { + const plugins = [ + [require('@babel/plugin-syntax-typescript'), { isTSX: true }], + [require('@babel/plugin-syntax-jsx')], + [ + require('babel-plugin-polyfill-corejs3'), + { + method: 'usage-pure', + targets: options?.targets, + }, + ], + ]; + return getBabelHook({ + plugins, + }); +}; + +export const modulePluginPolyfill = ( + options?: Options, +): CliPlugin => ({ name: '@modern-js/plugin-module-polyfill', setup: () => ({ beforeBuildTask(config) { - const plugins = [ - [require('@babel/plugin-syntax-typescript'), { isTSX: true }], - [require('@babel/plugin-syntax-jsx')], - [ - require('babel-plugin-polyfill-corejs3'), - { - method: 'usage-pure', - targets: options?.targets, - }, - ], - ]; - config.hooks.push( - getBabelHook({ - plugins, - }), - ); + config.hooks.push(getPolyfillHook(options)); return config; }, }), diff --git a/packages/solutions/module-tools/src/builder/build.ts b/packages/solutions/module-tools/src/builder/build.ts index 707f13c6306f..9067615935a1 100644 --- a/packages/solutions/module-tools/src/builder/build.ts +++ b/packages/solutions/module-tools/src/builder/build.ts @@ -8,6 +8,7 @@ import type { ModuleContext, } from '../types'; import pMap from '../../compiled/p-map'; +import { debug, label } from '../debug'; import { copyTask } from './copy'; import { createCompiler } from './esbuild'; @@ -52,35 +53,44 @@ export const generatorDts = async ( ) => { const { runRollup, runTsc } = await import('./dts'); const { watch, dts } = options; - const { buildType, input, sourceDir, alias, externals, tsconfig } = config; + const { + buildType, + input, + sourceDir, + alias, + externals, + tsconfig, + footer: { dts: footer }, + banner: { dts: banner }, + } = config; const { appDirectory } = api.useAppContext(); const { distPath, abortOnError, respectExternal } = dts; // remove this line after remove dts.tsconfigPath const tsconfigPath = dts.tsconfigPath ?? tsconfig; + const generatorDtsConfig = { + distPath, + watch, + externals, + input, + tsconfigPath, + abortOnError, + respectExternal, + appDirectory, + footer, + banner, + alias, + sourceDir, + }; + const prevTime = Date.now(); + debug(`${label('dts')} Build Start`); if (buildType === 'bundle') { - await runRollup(api, { - distDir: distPath, - watch, - externals, - input, - tsconfigPath, - abortOnError, - respectExternal, - appDirectory, - }); + await runRollup(api, generatorDtsConfig); } else { - await runTsc(api, { - appDirectory, - alias, - distAbsPath: distPath, - watch, - tsconfigPath, - sourceDir, - abortOnError, - }); + await runTsc(api, generatorDtsConfig); } + debug(`${label('dts')} Build success in ${Date.now() - prevTime}ms`); }; export const buildLib = async ( @@ -99,6 +109,8 @@ export const buildLib = async ( await checkSwcHelpers({ appDirectory, externalHelpers }); try { + const prevTime = Date.now(); + debug(`${label(config.format)} Build Start`); const compiler = await createCompiler({ config, watch, @@ -106,6 +118,9 @@ export const buildLib = async ( api, }); await compiler.build(); + debug( + `${label(config.format)} Build success in ${Date.now() - prevTime}ms`, + ); const { addOutputChunk } = await import('../utils/print'); addOutputChunk(compiler.outputChunk, root, buildType === 'bundle'); diff --git a/packages/solutions/module-tools/src/builder/copy.ts b/packages/solutions/module-tools/src/builder/copy.ts index bddb20723cf4..acb0adb3386b 100644 --- a/packages/solutions/module-tools/src/builder/copy.ts +++ b/packages/solutions/module-tools/src/builder/copy.ts @@ -1,12 +1,5 @@ import path from 'path'; -import { - watch, - fs, - logger, - createDebugger, - globby, - fastGlob, -} from '@modern-js/utils'; +import { watch, fs, logger, globby, fastGlob } from '@modern-js/utils'; import type { CopyOptions, CopyPattern } from '../types/config/copy'; import type { BaseBuildConfig } from '../types/config'; import pMap from '../../compiled/p-map'; @@ -156,8 +149,6 @@ export const watchCopyFiles = async ( }, copyConfig: CopyOptions, ) => { - const debug = createDebugger('module-tools:copy-watch'); - debug('watchMap', watchMap); const { SectionTitleStatus, CopyLogPrefix } = await import( diff --git a/packages/solutions/module-tools/src/builder/dts/rollup.ts b/packages/solutions/module-tools/src/builder/dts/rollup.ts index 2bab7e60aa9c..70b187eb7a19 100644 --- a/packages/solutions/module-tools/src/builder/dts/rollup.ts +++ b/packages/solutions/module-tools/src/builder/dts/rollup.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { logger } from '@modern-js/utils/logger'; +import { logger } from '@modern-js/utils'; import ts from 'typescript'; import type { InputOptions, @@ -8,7 +8,7 @@ import type { RollupWatcher, } from '../../../compiled/rollup'; import type { - BaseBuildConfig, + GeneratorDtsConfig, Input, PluginAPI, ModuleTools, @@ -19,21 +19,10 @@ import { mapValue, transformUndefineObject } from '../../utils'; export type { RollupWatcher }; -type Config = { - distDir: string; - tsconfigPath: string; - externals: BaseBuildConfig['externals']; - input: Input; - watch: boolean; - abortOnError: boolean; - respectExternal: boolean; - appDirectory: string; -}; - export const runRollup = async ( api: PluginAPI, { - distDir, + distPath, tsconfigPath, externals, input, @@ -41,7 +30,9 @@ export const runRollup = async ( abortOnError, respectExternal, appDirectory, - }: Config, + footer, + banner, + }: GeneratorDtsConfig, ) => { const ignoreFiles: Plugin = { name: 'ignore-files', @@ -114,9 +105,11 @@ export const runRollup = async ( ].filter(Boolean), }; const outputConfig: OutputOptions = { - dir: distDir, + dir: distPath, format: 'esm', exports: 'named', + footer, + banner, }; if (watch) { const { watch } = await import('../../../compiled/rollup'); diff --git a/packages/solutions/module-tools/src/builder/dts/tsc.ts b/packages/solutions/module-tools/src/builder/dts/tsc.ts index 65700e0975cb..b9c531bc85f5 100644 --- a/packages/solutions/module-tools/src/builder/dts/tsc.ts +++ b/packages/solutions/module-tools/src/builder/dts/tsc.ts @@ -1,17 +1,20 @@ import type { ChildProcess } from 'child_process'; import { execa, logger } from '@modern-js/utils'; import type { - BundlelessGeneratorDtsConfig, + GeneratorDtsConfig, PluginAPI, ModuleTools, + GeneratedDtsInfo, } from '../../types'; import { - generatorTsConfig, + generateDtsInfo, getTscBinPath, printOrThrowDtsErrors, resolveAlias, watchSectionTitle, addDtsFiles, + writeDtsFiles, + addBannerAndFooter, } from '../../utils'; import { BundlelessDtsLogPrefix, @@ -53,15 +56,14 @@ const resolveLog = async ( }); }; -const generatorDts = async ( +const runTscBin = async ( api: PluginAPI, - config: BundlelessGeneratorDtsConfig, + config: GeneratorDtsConfig, + info: GeneratedDtsInfo, ) => { const { appDirectory, watch = false, abortOnError = true } = config; - const { userTsconfig, generatedTsconfig: result } = await generatorTsConfig( - config, - ); + const { tempTsconfigPath } = info; const tscBinFile = await getTscBinPath(appDirectory); @@ -70,7 +72,7 @@ const generatorDts = async ( tscBinFile, [ '-p', - result.tempTsconfigPath, + tempTsconfigPath, /* Required parameter, use it stdout have color */ '--pretty', // https://github.com/microsoft/TypeScript/issues/21824 @@ -87,7 +89,9 @@ const generatorDts = async ( resolveLog(childProgress, { watch, watchFn: async () => { - await resolveAlias(config, { ...result, userTsconfig }); + const result = await resolveAlias(config, info); + const dtsFiles = addBannerAndFooter(result, config.banner, config.footer); + await writeDtsFiles(config, info, dtsFiles); runner.buildWatchDts({ buildType: 'bundleless' }); }, }); @@ -97,15 +101,16 @@ const generatorDts = async ( } catch (e) { await printOrThrowDtsErrors(e, { abortOnError, buildType: 'bundleless' }); } - - return { ...result, userTsconfig }; }; export const runTsc = async ( api: PluginAPI, - config: BundlelessGeneratorDtsConfig, + config: GeneratorDtsConfig, ) => { - const result = await generatorDts(api, config); - await resolveAlias(config, result); - await addDtsFiles(config.distAbsPath, config.appDirectory); + const generatedDtsInfo = await generateDtsInfo(config); + await runTscBin(api, config, generatedDtsInfo); + const result = await resolveAlias(config, generatedDtsInfo); + const dtsFiles = addBannerAndFooter(result, config.banner, config.footer); + await writeDtsFiles(config, generatedDtsInfo, dtsFiles); + await addDtsFiles(config.distPath, config.appDirectory); }; diff --git a/packages/solutions/module-tools/src/builder/esbuild/adapter.ts b/packages/solutions/module-tools/src/builder/esbuild/adapter.ts index 2c8e0e98c721..4286121ef68a 100644 --- a/packages/solutions/module-tools/src/builder/esbuild/adapter.ts +++ b/packages/solutions/module-tools/src/builder/esbuild/adapter.ts @@ -6,7 +6,7 @@ import { fs, isString } from '@modern-js/utils'; import { createFilter } from '@rollup/pluginutils'; import { normalizeSourceMap, resolvePathAndQuery } from '../../utils'; import { loaderMap } from '../../constants/loader'; -import { debug } from '../../debug'; +import { debugResolve } from '../../debug'; import type { SideEffects, ICompiler } from '../../types'; import { writeFile } from './write-file'; import { initWatcher } from './watch'; @@ -45,8 +45,6 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { }); build.onResolve({ filter: /.*/ }, async args => { - debug('onResolve', args.path); - if (args.kind === 'url-token') { return { path: args.path, @@ -57,6 +55,7 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { for (const [key] of Object.entries(config.umdGlobals)) { const isMatch = pm(key); if (isMatch(args.path)) { + debugResolve('resolve umdGlobals:', key); return { path: args.path, namespace: globalNamespace, @@ -192,7 +191,7 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { const dir = args.resolveDir ?? (args.importer ? dirname(args.importer) : root); const sideEffects = await getSideEffects(originalFilePath, isExternal); - return { + const result = { path: isExternal ? args.path : getResultPath(originalFilePath, dir, args.kind), @@ -201,10 +200,11 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { sideEffects, suffix, }; + debugResolve('onResolve args:', args); + debugResolve('onResolve result:', result); + return result; }); build.onLoad({ filter: /.*/ }, async args => { - debug('onLoad', args.path); - if (args.namespace === globalNamespace) { const value = config.umdGlobals[args.path]; return { @@ -263,7 +263,6 @@ export const adapterPlugin = (compiler: ICompiler): Plugin => { }); build.onEnd(async result => { - debug('onEnd'); if (result.errors.length) { return; } diff --git a/packages/solutions/module-tools/src/builder/esbuild/index.ts b/packages/solutions/module-tools/src/builder/esbuild/index.ts index 03cb77e68a92..ca8ae76254be 100644 --- a/packages/solutions/module-tools/src/builder/esbuild/index.ts +++ b/packages/solutions/module-tools/src/builder/esbuild/index.ts @@ -10,7 +10,7 @@ import { Format, } from 'esbuild'; import * as tapable from 'tapable'; -import { FSWatcher, chalk, logger, fs } from '@modern-js/utils'; +import { FSWatcher, chalk, logger, fs, lodash } from '@modern-js/utils'; import { BaseBuildConfig, BuilderHooks, @@ -104,7 +104,7 @@ export class EsbuildCompiler implements ICompiler { const internal = await getInternalList(this.context); const user = this.config.hooks; this.hookList = [...user, ...internal]; - await Promise.all(this.hookList.map(item => item.hooks(this))); + await Promise.all(this.hookList.map(item => item.apply(this))); } addWatchFile(id: string): void { @@ -131,6 +131,8 @@ export class EsbuildCompiler implements ICompiler { format, asset, tsconfig, + banner, + footer, } = config; const bundle = buildType === 'bundle'; @@ -142,7 +144,10 @@ export class EsbuildCompiler implements ICompiler { } const esbuildTarget = target === 'es5' ? undefined : target; const jsExtensions = ['.jsx', '.tsx', '.js', '.ts', '.json']; + const buildOptions: BuildOptions = { + banner: lodash.pick(banner, ['js', 'css']), + footer: lodash.pick(footer, ['js', 'css']), entryPoints: input, metafile: true, define, diff --git a/packages/solutions/module-tools/src/builder/feature/asset.ts b/packages/solutions/module-tools/src/builder/feature/asset.ts index dfe873984158..edfd659d08ec 100644 --- a/packages/solutions/module-tools/src/builder/feature/asset.ts +++ b/packages/solutions/module-tools/src/builder/feature/asset.ts @@ -14,7 +14,7 @@ const SVG_REGEXP = /\.svg$/; export const asset = { name, - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { compiler.hooks.load.tapPromise({ name }, async args => { if (assetExt.find(ext => ext === extname(args.path))) { const { buildType, outDir, sourceDir, asset } = compiler.config; diff --git a/packages/solutions/module-tools/src/builder/feature/format-cjs.ts b/packages/solutions/module-tools/src/builder/feature/format-cjs.ts index 2719ed386e9c..05db1e15980f 100644 --- a/packages/solutions/module-tools/src/builder/feature/format-cjs.ts +++ b/packages/solutions/module-tools/src/builder/feature/format-cjs.ts @@ -2,7 +2,7 @@ import { transform } from 'sucrase'; import { ICompiler } from '../../types'; const name = 'format-cjs'; -const hooks = (compiler: ICompiler) => { +const apply = (compiler: ICompiler) => { compiler.hooks.renderChunk.tapPromise({ name }, async chunk => { if (chunk.fileName.endsWith('.js') && chunk.type === 'chunk') { const code = chunk.contents.toString(); @@ -21,5 +21,5 @@ const hooks = (compiler: ICompiler) => { export const formatCjs = { name, - hooks, + apply, }; diff --git a/packages/solutions/module-tools/src/builder/feature/json.ts b/packages/solutions/module-tools/src/builder/feature/json.ts index a0d0931f2277..e820d5f34a64 100644 --- a/packages/solutions/module-tools/src/builder/feature/json.ts +++ b/packages/solutions/module-tools/src/builder/feature/json.ts @@ -8,7 +8,7 @@ const isJsonExt = (path: string) => { const name = 'json'; export const json = { name, - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { compiler.hooks.load.tapPromise({ name }, async args => { if (isJsonExt(args.path)) { return { diff --git a/packages/solutions/module-tools/src/builder/feature/redirect.ts b/packages/solutions/module-tools/src/builder/feature/redirect.ts index 2c822466cde0..171a1b8cb999 100644 --- a/packages/solutions/module-tools/src/builder/feature/redirect.ts +++ b/packages/solutions/module-tools/src/builder/feature/redirect.ts @@ -14,7 +14,7 @@ import { import { js } from '@ast-grep/napi'; import MagicString from 'magic-string'; import { createMatchPath, loadConfig, MatchPath } from 'tsconfig-paths'; -import { fs } from '@modern-js/utils'; +import { fs, logger } from '@modern-js/utils'; import { ICompiler } from '../../types'; import { assetExt } from '../../constants/file'; import { @@ -157,7 +157,7 @@ async function redirectImport( const name = 'redirect'; export const redirect = { name, - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { // get matchPath func to support tsconfig paths let matchPath: MatchPath | undefined; if (fs.existsSync(compiler.config.tsconfig)) { @@ -232,7 +232,7 @@ export const redirect = { }); matchModule = [...funcMatchModule, ...staticMatchModule]; } catch (e) { - console.error('[parse error]', e); + logger.error('[parse error]', e); } if (!matchModule.length) { return args; diff --git a/packages/solutions/module-tools/src/builder/feature/style/index.ts b/packages/solutions/module-tools/src/builder/feature/style/index.ts index 4a59fc5c85fd..a38fefd65e89 100644 --- a/packages/solutions/module-tools/src/builder/feature/style/index.ts +++ b/packages/solutions/module-tools/src/builder/feature/style/index.ts @@ -7,7 +7,7 @@ import { transformStyle } from './transformStyle'; const name = 'css'; export const css = { name, - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { compiler.hooks.load.tapPromise({ name }, async args => { if (isStyleExt(args.path)) { const { query } = resolvePathAndQuery(args.path); diff --git a/packages/solutions/module-tools/src/builder/feature/swc.ts b/packages/solutions/module-tools/src/builder/feature/swc.ts index 121aea9b5c40..46299dad3c45 100644 --- a/packages/solutions/module-tools/src/builder/feature/swc.ts +++ b/packages/solutions/module-tools/src/builder/feature/swc.ts @@ -41,7 +41,7 @@ const getSwcTarget = (target: string): JscTarget => { export const swcTransform = (userTsconfig: ITsconfig) => ({ name, - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { const tsUseDefineForClassFields = userTsconfig?.compilerOptions?.useDefineForClassFields; const emitDecoratorMetadata = @@ -137,7 +137,7 @@ export const swcTransform = (userTsconfig: ITsconfig) => ({ export const swcRenderChunk = { name: 'swc:renderChunk', - hooks(compiler: ICompiler) { + apply(compiler: ICompiler) { compiler.hooks.renderChunk.tapPromise( { name: 'swc:renderChunk' }, async chunk => { diff --git a/packages/solutions/module-tools/src/builder/feature/terser.ts b/packages/solutions/module-tools/src/builder/feature/terser.ts index fe5a8a01e817..ed80495bf67f 100644 --- a/packages/solutions/module-tools/src/builder/feature/terser.ts +++ b/packages/solutions/module-tools/src/builder/feature/terser.ts @@ -7,7 +7,7 @@ import { ChunkType, ICompiler } from '../../types'; import { normalizeSourceMap } from '../../utils'; const name = 'terser'; -const hooks = (compiler: ICompiler) => { +const apply = (compiler: ICompiler) => { compiler.hooks.renderChunk.tapPromise({ name }, async chunk => { const { sourceMap, minify } = compiler.config; if (chunk.type === ChunkType.chunk) { @@ -35,7 +35,7 @@ const hooks = (compiler: ICompiler) => { export const minify = { name, - hooks, + apply, }; function resolveTerserOptions( diff --git a/packages/solutions/module-tools/src/builder/index.ts b/packages/solutions/module-tools/src/builder/index.ts index 14c0bff8f48e..123c225fce47 100644 --- a/packages/solutions/module-tools/src/builder/index.ts +++ b/packages/solutions/module-tools/src/builder/index.ts @@ -44,7 +44,9 @@ export const run = async ( await pMap( resolvedBuildConfig, async config => { + debug('run beforeBuildTask hooks'); const buildConfig = await runner.beforeBuildTask(config); + debug('run beforeBuildTask hooks done'); await runBuildTask( { buildConfig, @@ -53,7 +55,9 @@ export const run = async ( }, api, ); + debug('run afterBuildTask hooks'); await runner.afterBuildTask({ status: 'success', config }); + debug('run afterBuildTask hooks done'); }, { concurrency: os.cpus().length }, ); diff --git a/packages/solutions/module-tools/src/config/merge.ts b/packages/solutions/module-tools/src/config/merge.ts index de725926dc2e..61405b5c1cec 100644 --- a/packages/solutions/module-tools/src/config/merge.ts +++ b/packages/solutions/module-tools/src/config/merge.ts @@ -113,6 +113,8 @@ export const mergeDefaultBaseConfig = async ( const esbuildOptions = pConfig.esbuildOptions ?? defaultConfig.esbuildOptions; return { + footer: pConfig.footer ?? defaultConfig.footer, + banner: pConfig.banner ?? defaultConfig.banner, resolve, tsconfig, hooks: pConfig.hooks ?? defaultConfig.hooks, diff --git a/packages/solutions/module-tools/src/config/schema.ts b/packages/solutions/module-tools/src/config/schema.ts index 8d849e14498d..fe230c1432d8 100644 --- a/packages/solutions/module-tools/src/config/schema.ts +++ b/packages/solutions/module-tools/src/config/schema.ts @@ -34,8 +34,17 @@ const buildConfigProperties = { typeof: ['object', 'function'], }, hooks: { + // TODO: improve it type: 'array', }, + resolve: { + // TODO: add properties + type: 'object', + }, + banner: { + // TODO: add properties + type: 'object', + }, asset: { type: 'object', }, diff --git a/packages/solutions/module-tools/src/constants/build.ts b/packages/solutions/module-tools/src/constants/build.ts index 116a70e20a6c..255b728c6784 100644 --- a/packages/solutions/module-tools/src/constants/build.ts +++ b/packages/solutions/module-tools/src/constants/build.ts @@ -10,6 +10,7 @@ export const getDefaultBuildConfig = () => { svgr: false, }, autoExternal: true, + banner: {}, buildType: 'bundle', copy: {}, define: {}, @@ -25,6 +26,7 @@ export const getDefaultBuildConfig = () => { externalHelpers: false, externals: [], format: 'cjs', + footer: {}, hooks: [], input: ['src/index.ts'], jsx: 'automatic', diff --git a/packages/solutions/module-tools/src/debug.ts b/packages/solutions/module-tools/src/debug.ts index fe7014fe80c1..abc06b7d9752 100644 --- a/packages/solutions/module-tools/src/debug.ts +++ b/packages/solutions/module-tools/src/debug.ts @@ -1,3 +1,9 @@ -import { debug as Debugger } from '@modern-js/utils'; +import { debug as Debugger, chalk } from '@modern-js/utils'; export const debug = Debugger('module'); + +export const label = (info: string) => { + return chalk.blue(`[${info.toUpperCase()}]`); +}; + +export const debugResolve = Debugger('module:resolve'); diff --git a/packages/solutions/module-tools/src/hooks/misc.ts b/packages/solutions/module-tools/src/hooks/misc.ts index c1414063ceec..4f7940289232 100644 --- a/packages/solutions/module-tools/src/hooks/misc.ts +++ b/packages/solutions/module-tools/src/hooks/misc.ts @@ -1,5 +1,10 @@ -import { createAsyncWaterfall } from '@modern-js/plugin'; +import { createAsyncWaterfall, createAsyncPipeline } from '@modern-js/plugin'; export const miscHooks = { addRuntimeExports: createAsyncWaterfall(), + /** + * @deprecated + * use buildConfig.hooks and buildConfig.esbuildOptions instead. + */ + modifyLibuild: createAsyncPipeline(), }; diff --git a/packages/solutions/module-tools/src/types/config/index.ts b/packages/solutions/module-tools/src/types/config/index.ts index cb0376bb3003..12d967775104 100644 --- a/packages/solutions/module-tools/src/types/config/index.ts +++ b/packages/solutions/module-tools/src/types/config/index.ts @@ -18,7 +18,7 @@ export * from './copy'; export type HookList = { name: string; - hooks: (compiler: ICompiler) => void; + apply: (compiler: ICompiler) => void; }[]; export type EsbuildOptions = (options: BuildOptions) => BuildOptions; @@ -116,6 +116,12 @@ export type JSX = 'automatic' | 'transform' | 'preserve'; export type ExternalHelpers = boolean; +export type BannerAndFooter = { + js?: string; + css?: string; + dts?: string; +}; + export type AliasOption = | Record | ((aliases: Record) => Record | void); @@ -139,6 +145,8 @@ export type BaseBuildConfig = Omit< export type PartialBaseBuildConfig = { resolve?: Resolve; + footer?: BannerAndFooter; + banner?: BannerAndFooter; buildType?: 'bundleless' | 'bundle'; format?: Format; target?: Target; @@ -171,7 +179,7 @@ export type PartialBaseBuildConfig = { * cache transform result or not */ transformCache?: boolean; - // Related to swc-transform + // The following is related to swc-transform externalHelpers?: ExternalHelpers; transformImport?: ImportItem[]; transformLodash?: boolean; diff --git a/packages/solutions/module-tools/src/types/dts.ts b/packages/solutions/module-tools/src/types/dts.ts index be5eb4cd7f07..34a6d1000df2 100644 --- a/packages/solutions/module-tools/src/types/dts.ts +++ b/packages/solutions/module-tools/src/types/dts.ts @@ -1,13 +1,25 @@ -import type { AliasOption } from './config'; +import type { AliasOption, BaseBuildConfig, Input } from './config'; -export interface BundlelessGeneratorDtsConfig { +export interface GeneratorDtsConfig { appDirectory: string; - distAbsPath: string; tsconfigPath: string; watch: boolean; sourceDir: string; alias: AliasOption; abortOnError?: boolean; + footer?: string; + banner?: string; + distPath: string; + externals: BaseBuildConfig['externals']; + input: Input; + respectExternal: boolean; +} + +export interface GeneratedDtsInfo { + userTsconfig: ITsconfig; + tempTsconfigPath: string; + tempDistAbsRootPath: string; + tempDistAbsSrcPath: string; } export type TsTarget = diff --git a/packages/solutions/module-tools/src/utils/dts.ts b/packages/solutions/module-tools/src/utils/dts.ts index 588eedbf379e..9753957c5809 100644 --- a/packages/solutions/module-tools/src/utils/dts.ts +++ b/packages/solutions/module-tools/src/utils/dts.ts @@ -1,8 +1,9 @@ import { join, dirname, isAbsolute, relative, resolve, basename } from 'path'; import { chalk, fs, globby, json5, logger, nanoid } from '@modern-js/utils'; +import MagicString from 'magic-string'; import type { ITsconfig, - BundlelessGeneratorDtsConfig, + GeneratorDtsConfig, BuildType, TsTarget, } from '../types'; @@ -17,9 +18,7 @@ export const getProjectTsconfig = async ( return json5.parse(fs.readFileSync(tsconfigPath, 'utf-8')); }; -export const generatorTsConfig = async ( - config: BundlelessGeneratorDtsConfig, -) => { +export const generateDtsInfo = async (config: GeneratorDtsConfig) => { const { appDirectory, sourceDir: absSourceDir, tsconfigPath } = config; const userTsconfig = await getProjectTsconfig(tsconfigPath); @@ -75,11 +74,9 @@ export const generatorTsConfig = async ( return { userTsconfig, - generatedTsconfig: { - tempTsconfigPath, - tempDistAbsRootPath, - tempDistAbsSrcPath: tempDistAbsOurDir, - }, + tempTsconfigPath, + tempDistAbsRootPath, + tempDistAbsSrcPath: tempDistAbsOurDir, }; }; @@ -109,7 +106,7 @@ export const getTscBinPath = async (appDirectory: string) => { }; export const resolveAlias = async ( - config: BundlelessGeneratorDtsConfig, + config: GeneratorDtsConfig, options: { userTsconfig: ITsconfig; tempTsconfigPath: string; @@ -120,7 +117,6 @@ export const resolveAlias = async ( ) => { const { userTsconfig, tempDistAbsSrcPath, tempDistAbsRootPath } = options; const { transformDtsAlias } = await import('./tspath'); - const { distAbsPath } = config; const dtsDistPath = `${tempDistAbsSrcPath}/**/*.d.ts`; const dtsFilenames = watchFilenames.length > 0 @@ -131,13 +127,45 @@ export const resolveAlias = async ( baseUrl: tempDistAbsRootPath, paths: userTsconfig.compilerOptions?.paths ?? {}, }); + return result; +}; + +export const writeDtsFiles = async ( + config: GeneratorDtsConfig, + options: { + userTsconfig: ITsconfig; + tempTsconfigPath: string; + tempDistAbsRootPath: string; + tempDistAbsSrcPath: string; + }, + result: { path: string; content: string }[], +) => { + const { distPath } = config; + const { tempDistAbsSrcPath } = options; + for (const r of result) { fs.writeFileSync(r.path, r.content); } // why use `ensureDir` before copy? look this: https://github.com/jprichardson/node-fs-extra/issues/957 - await fs.ensureDir(distAbsPath); - await fs.copy(tempDistAbsSrcPath, distAbsPath); + await fs.ensureDir(distPath); + await fs.copy(tempDistAbsSrcPath, distPath); +}; + +export const addBannerAndFooter = ( + result: { path: string; content: string }[], + banner?: string, + footer?: string, +) => { + return result.map(({ path, content }) => { + const ms = new MagicString(content); + banner && ms.prepend(`${banner}\n`); + footer && ms.append(`\n${footer}\n`); + return { + path, + content: ms.toString(), + }; + }); }; export const printOrThrowDtsErrors = async ( diff --git a/packages/solutions/module-tools/tests/generatorTsConfig.test.ts b/packages/solutions/module-tools/tests/generateDtsInfo.test.ts similarity index 61% rename from packages/solutions/module-tools/tests/generatorTsConfig.test.ts rename to packages/solutions/module-tools/tests/generateDtsInfo.test.ts index acb7caa898ad..30049b21bf5b 100644 --- a/packages/solutions/module-tools/tests/generatorTsConfig.test.ts +++ b/packages/solutions/module-tools/tests/generateDtsInfo.test.ts @@ -1,22 +1,26 @@ import { resolve } from 'path'; import { fs } from '@modern-js/utils'; -import { generatorTsConfig } from '../src/utils/dts'; +import { generateDtsInfo } from '../src/utils/dts'; describe('utils', () => { - it('generatorTsConfig', async () => { + it('generateDtsInfo', async () => { const appDirectory = resolve(__dirname, 'fixtures/example'); const tsconfigPath = resolve(appDirectory, 'tsconfig.json'); const sourceDir = resolve(appDirectory, 'src'); - const distAbsPath = resolve(appDirectory, 'dist'); - const { - generatedTsconfig: { tempTsconfigPath }, - } = await generatorTsConfig({ + const distPath = resolve(appDirectory, 'dist'); + const { tempTsconfigPath } = await generateDtsInfo({ appDirectory, - distAbsPath, + distPath, tsconfigPath, watch: false, sourceDir, alias: {}, + // not influence the result + footer: '', + banner: '', + externals: [], + input: [], + respectExternal: true, }); const content = await fs.readFile(tempTsconfigPath, 'utf8'); expect(content.includes('references')).toBeTruthy(); diff --git a/tests/integration/module/fixtures/build/banner-footer/banner.test.ts b/tests/integration/module/fixtures/build/banner-footer/banner.test.ts new file mode 100644 index 000000000000..2a7244b01fdd --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/banner.test.ts @@ -0,0 +1,52 @@ +import path from 'path'; +import { fs } from '@modern-js/utils'; +import { runCli, initBeforeTest } from '../../utils'; + +initBeforeTest(); + +describe('banner and footer', () => { + const fixtureDir = __dirname; + it('buildType is bundle', async () => { + const configFile = path.join(fixtureDir, './bundle.config.ts'); + await runCli({ + argv: ['build'], + configFile, + appDirectory: fixtureDir, + enableDts: true, + }); + const [js, css, dts] = await Promise.all([ + fs.readFile(path.join(fixtureDir, 'dist/bundle/index.js'), 'utf8'), + fs.readFile(path.join(fixtureDir, 'dist/bundle/index.css'), 'utf8'), + fs.readFile(path.join(fixtureDir, 'dist/bundle/index.d.ts'), 'utf8'), + ]); + + expect(js).toContain('js banner'); + expect(js).toContain('js banner'); + expect(css).toContain('css banner'); + expect(css).toContain('css banner'); + expect(dts).toContain('dts banner'); + expect(dts).toContain('dts banner'); + }); + + it('buildType is bundleless', async () => { + const configFile = path.join(fixtureDir, './bundleless.config.ts'); + await runCli({ + argv: ['build'], + configFile, + appDirectory: fixtureDir, + enableDts: true, + }); + const [js, css, dts] = await Promise.all([ + fs.readFile(path.join(fixtureDir, 'dist/bundleless/index.js'), 'utf8'), + fs.readFile(path.join(fixtureDir, 'dist/bundleless/index.css'), 'utf8'), + fs.readFile(path.join(fixtureDir, 'dist/bundleless/index.d.ts'), 'utf8'), + ]); + + expect(js).toContain('js footer'); + expect(js).toContain('js footer'); + expect(css).toContain('css footer'); + expect(css).toContain('css footer'); + expect(dts).toContain('dts footer'); + expect(dts).toContain('dts footer'); + }); +}); diff --git a/tests/integration/module/fixtures/build/banner-footer/bundle.config.ts b/tests/integration/module/fixtures/build/banner-footer/bundle.config.ts new file mode 100644 index 000000000000..3b58b2502d5b --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/bundle.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from '@modern-js/module-tools/defineConfig'; + +export default defineConfig({ + buildConfig: { + buildType: 'bundle', + outDir: './dist/bundle', + banner: { + js: '/* js banner */', + css: '/* css banner */', + dts: '/* dts banner */', + }, + footer: { + js: '/* js footer */', + css: '/* css footer */', + dts: '/* dts footer */', + }, + }, +}); diff --git a/tests/integration/module/fixtures/build/banner-footer/bundleless.config.ts b/tests/integration/module/fixtures/build/banner-footer/bundleless.config.ts new file mode 100644 index 000000000000..de56b25a51ab --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/bundleless.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from '@modern-js/module-tools/defineConfig'; + +export default defineConfig({ + buildConfig: { + buildType: 'bundleless', + outDir: './dist/bundleless', + banner: { + js: '/* js banner */', + css: '/* css banner */', + dts: '/* dts banner */', + }, + footer: { + js: '/* js footer */', + css: '/* css footer */', + dts: '/* dts footer */', + }, + }, +}); diff --git a/tests/integration/module/fixtures/build/banner-footer/package.json b/tests/integration/module/fixtures/build/banner-footer/package.json new file mode 100644 index 000000000000..1a1cf3276981 --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/package.json @@ -0,0 +1,4 @@ +{ + "name": "banner-footer-test", + "version": "1.0.0" +} diff --git a/tests/integration/module/fixtures/build/banner-footer/src/index.css b/tests/integration/module/fixtures/build/banner-footer/src/index.css new file mode 100644 index 000000000000..0916ff25aac1 --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/src/index.css @@ -0,0 +1,3 @@ +.a { + color: black; +} diff --git a/tests/integration/module/fixtures/build/banner-footer/src/index.ts b/tests/integration/module/fixtures/build/banner-footer/src/index.ts new file mode 100644 index 000000000000..e72d58b62b16 --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/src/index.ts @@ -0,0 +1,3 @@ +import './index.css'; + +export const a = 'a'; diff --git a/tests/integration/module/fixtures/build/banner-footer/tsconfig.json b/tests/integration/module/fixtures/build/banner-footer/tsconfig.json new file mode 100644 index 000000000000..7e855a64bb31 --- /dev/null +++ b/tests/integration/module/fixtures/build/banner-footer/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["src"] +}