diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index decc5d8b3..772740169 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -783,6 +783,8 @@ importers: tests/integration/format: {} + tests/integration/json: {} + tests/integration/minify/config/disabled: {} tests/integration/minify/config/enabled: {} diff --git a/tests/integration/json/index.test.ts b/tests/integration/json/index.test.ts new file mode 100644 index 000000000..c8c27df79 --- /dev/null +++ b/tests/integration/json/index.test.ts @@ -0,0 +1,63 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { buildAndGetResults, queryContent } from 'test-helper'; +import { describe, expect, test } from 'vitest'; + +describe('JSON', async () => { + const fixturePath = join(__dirname, '.'); + const { contents, files } = await buildAndGetResults({ fixturePath }); + + test('bundle', async () => { + const { content: bundle } = queryContent(contents.esm0!, /index\.js/); + expect(bundle).toMatchInlineSnapshot(` + "var foo_namespaceObject = { + S: "foo" + }; + const src = foo_namespaceObject.S + '1'; + export { src as default }; + " + `); + const bundleResult = await import(files.esm0![0]!); + expect(bundleResult.default).toBe('foo1'); + }); + + test('bundleless default', async () => { + const bundlelessFiles = Object.keys(contents.esm1!); + expect(bundlelessFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/json/dist/bundleless-default/foo.js", + "/tests/integration/json/dist/bundleless-default/index.js", + ] + `); + const bundlelessResult = await import( + files.esm1!.find((file) => file.endsWith('index.js'))! + ); + expect(bundlelessResult.default).toBe('foo1'); + }); + + test('bundleless preserve JSON', async () => { + const { content: bundlelessPreserveJson } = queryContent( + contents.esm2!, + /index\.js/, + ); + expect(bundlelessPreserveJson).toMatchInlineSnapshot(` + "import * as __WEBPACK_EXTERNAL_MODULE__foo_json_16d256d4__ from "./foo.json"; + const src = __WEBPACK_EXTERNAL_MODULE__foo_json_16d256d4__.value + '1'; + export { src as default }; + " + `); + + expect( + readFileSync( + join(fixturePath, 'dist/bundleless-preserve-json/foo.json'), + 'utf-8', + ), + ).toMatchInlineSnapshot(` + "{ + "value": "foo", + "value_unused": "noop" + } + " + `); + }); +}); diff --git a/tests/integration/json/package.json b/tests/integration/json/package.json new file mode 100644 index 000000000..0551f1cde --- /dev/null +++ b/tests/integration/json/package.json @@ -0,0 +1,6 @@ +{ + "name": "json-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/json/rslib.config.ts b/tests/integration/json/rslib.config.ts new file mode 100644 index 000000000..3684bd4c2 --- /dev/null +++ b/tests/integration/json/rslib.config.ts @@ -0,0 +1,45 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + // bundle default + generateBundleEsmConfig({ + output: { + distPath: { + root: './dist/bundle-default', + }, + }, + }), + // bundleless default + generateBundleEsmConfig({ + bundle: false, + source: { + entry: { + index: ['./src/**'], + }, + }, + output: { + distPath: { + root: './dist/bundleless-default', + }, + }, + }), + // bundleless preserve JSON + generateBundleEsmConfig({ + bundle: false, + source: { + entry: { + index: ['./src/**', '!./src/**/*.json'], + }, + }, + output: { + copy: [{ from: './**/*.json', context: './src' }], + externals: [/.*\.json$/], + distPath: { + root: './dist/bundleless-preserve-json', + }, + }, + }), + ], +}); diff --git a/tests/integration/json/src/foo.json b/tests/integration/json/src/foo.json new file mode 100644 index 000000000..eeaab156b --- /dev/null +++ b/tests/integration/json/src/foo.json @@ -0,0 +1,4 @@ +{ + "value": "foo", + "value_unused": "noop" +} diff --git a/tests/integration/json/src/index.ts b/tests/integration/json/src/index.ts new file mode 100644 index 000000000..706bb6deb --- /dev/null +++ b/tests/integration/json/src/index.ts @@ -0,0 +1,3 @@ +import { value as foo } from './foo.json'; + +export default foo + '1'; diff --git a/tests/integration/json/tsconfig.json b/tests/integration/json/tsconfig.json new file mode 100644 index 000000000..48845188d --- /dev/null +++ b/tests/integration/json/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@rslib/tsconfig/base", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["./src"] +} diff --git a/website/docs/en/guide/advanced/json-files.mdx b/website/docs/en/guide/advanced/json-files.mdx index aae3b6dac..d4a3c7618 100644 --- a/website/docs/en/guide/advanced/json-files.mdx +++ b/website/docs/en/guide/advanced/json-files.mdx @@ -222,3 +222,42 @@ declare module '*.toml' { export default content; } ``` + +## Bundle mode and output + +Rslib supports outputting JSON / YAML / TOML files in different forms under different bundle modes. + +### bundle + +In bundle mode ([`bundle: true`](/config/lib/bundle)), JSON files will be directly bundled into JavaScript output, and unused keys in JSON files will be tree-shaken. The same applies to TOML and YAML files. + +### bundleless + +In bundleless mode ([`bundle: false`](/config/lib/bundle)), each JSON / YAML / TOML file will be converted into a corresponding JavaScript output file. JSON files will be converted to `JSON.parse` form and exported, while YAML and TOML files will be converted to JavaScript objects and exported. + +If you want JSON / YAML / TOML files to be output to the distribution directory as-is, and keep the reference paths to these files in the output JavaScript files, you can achieve this through the following steps: + +1. Exclude JSON / YAML / TOML files from the [bundleless](/config/rsbuild/source#sourceentry) entry file glob pattern. +2. Reserve request paths for JSON / YAML / TOML files in [output.externals](/config/rsbuild/output#outputexternals). +3. Add [output.copy](/config/rsbuild/output#outputcopy) option to the output configuration, specifying the output path for JSON / YAML / TOML files. + +For example, the following configuration will output all JSON files in the `src` directory as-is: + +```ts title="rslib.config.ts" {7,11-19} +export default defineConfig({ + lib: [ + { + bundle: false, + source: { + entry: { + index: ['./src/**', '!./src/**/*.json'], + }, + }, + output: { + copy: [{ from: './**/*.json', context: './src' }], + externals: [/.*\.json$/], + }, + }, + ], +}); +``` diff --git a/website/docs/zh/guide/advanced/json-files.mdx b/website/docs/zh/guide/advanced/json-files.mdx index 44a24ca8d..d73344e21 100644 --- a/website/docs/zh/guide/advanced/json-files.mdx +++ b/website/docs/zh/guide/advanced/json-files.mdx @@ -219,3 +219,42 @@ declare module '*.toml' { export default content; } ``` + +## 打包模式与输出 + +Rslib 支持在不同的打包模式下,JSON / YAML / TOML 文件以不同的形式输出。 + +### bundle + +在 bundle 模式下(即 [`bundle: true`](/config/lib/bundle)),JSON 文件会被直接打包到 JavaScript 产物中,且 JSON 文件中没有被使用到的 key 会被 tree-shake 掉,TOML 和 YAML 文件同理。 + +### bundleless + +在 bundleless 模式下(即 [`bundle: false`](/config/lib/bundle)),每个 JSON / YAML / TOML 文件会被转换为对应的 JavaScript 模块输出,JSON 文件会被转换为 `JSON.parse` 的形式并导出,YAML 和 TOML 文件会被转换为 JavaScript 对象并导出。 + +如果希望 JSON / YAML / TOML 文件按原样输出到产物目录,并且产物 JavaScript 文件中保留对这些文件的引用路径,可以通过以下方式完成: + +1. 在 [source.entry](/config/rsbuild/source#sourceentry) 入口文件的 glob 匹配中忽略 JSON / YAML / TOML 文件 +2. 在 [output.externals](/config/rsbuild/output#outputexternals) 中保留 JSON / YAML / TOML 文件的请求路径 +3. 在产物输出中添加 [output.copy](/config/rsbuild/output#outputcopy) 选项,指定 JSON / YAML / TOML 文件的输出路径 + +例如下面的配置将会将 `src` 目录下的所有 JSON 文件按原样输出: + +```ts title="rslib.config.ts" {7,11-19} +export default defineConfig({ + lib: [ + { + bundle: false, + source: { + entry: { + index: ['./src/**', '!./src/**/*.json'], + }, + }, + output: { + copy: [{ from: './**/*.json', context: './src' }], + externals: [/.*\.json$/], + }, + }, + ], +}); +```