From 9813476ec3b85ad371bbff9babed6c9f1ffbbda7 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Thu, 30 Oct 2025 22:21:45 +0900 Subject: [PATCH] Fix loader issue --- .changeset/eight-rings-sleep.md | 6 ++ .../src/__tests__/css-loader.test.ts | 2 +- .../next-plugin/src/__tests__/loader.test.ts | 4 +- .../next-plugin/src/__tests__/plugin.test.ts | 35 ++++++++--- .../next-plugin/src/__tests__/preload.test.ts | 37 ++++++------ packages/next-plugin/src/css-loader.ts | 21 ++++++- packages/next-plugin/src/loader.ts | 47 ++++++--------- packages/next-plugin/src/plugin.ts | 18 ++++-- packages/next-plugin/src/preload.ts | 8 +-- .../src/__tests__/loader.test.ts | 59 +------------------ .../src/__tests__/plugin.test.ts | 10 +++- packages/webpack-plugin/src/loader.ts | 29 +-------- packages/webpack-plugin/src/plugin.ts | 4 +- 13 files changed, 119 insertions(+), 161 deletions(-) create mode 100644 .changeset/eight-rings-sleep.md diff --git a/.changeset/eight-rings-sleep.md b/.changeset/eight-rings-sleep.md new file mode 100644 index 00000000..82b4119b --- /dev/null +++ b/.changeset/eight-rings-sleep.md @@ -0,0 +1,6 @@ +--- +'@devup-ui/webpack-plugin': patch +'@devup-ui/next-plugin': patch +--- + +Fix turbo build issue, split webpack, turbopack loader diff --git a/packages/next-plugin/src/__tests__/css-loader.test.ts b/packages/next-plugin/src/__tests__/css-loader.test.ts index 51f8eba9..b27dc311 100644 --- a/packages/next-plugin/src/__tests__/css-loader.test.ts +++ b/packages/next-plugin/src/__tests__/css-loader.test.ts @@ -26,7 +26,7 @@ describe('devupUICssLoader', () => { resourcePath: 'devup-ui.css', getOptions: () => ({ watch: false }), } as any)(Buffer.from('data'), '') - expect(callback).toBeCalledWith(null, 'get css', '', undefined) + expect(callback).toBeCalledWith(null, Buffer.from('data'), '', undefined) }) it('should return _compiler hit css on watch', () => { diff --git a/packages/next-plugin/src/__tests__/loader.test.ts b/packages/next-plugin/src/__tests__/loader.test.ts index 6d3183f6..7ab9a8aa 100644 --- a/packages/next-plugin/src/__tests__/loader.test.ts +++ b/packages/next-plugin/src/__tests__/loader.test.ts @@ -139,7 +139,9 @@ describe('devupUILoader', () => { false, true, ) - expect(t.async()).toHaveBeenCalledWith(null, 'code', null) + await vi.waitFor(() => { + expect(t.async()).toHaveBeenCalledWith(null, 'code', null) + }) expect(writeFile).not.toHaveBeenCalledWith('cssFile', 'css', { encoding: 'utf-8', }) diff --git a/packages/next-plugin/src/__tests__/plugin.test.ts b/packages/next-plugin/src/__tests__/plugin.test.ts index c8a9097b..1623840a 100644 --- a/packages/next-plugin/src/__tests__/plugin.test.ts +++ b/packages/next-plugin/src/__tests__/plugin.test.ts @@ -91,6 +91,9 @@ describe('DevupUINextPlugin', () => { './df/devup-ui/*.css': [ { loader: '@devup-ui/next-plugin/css-loader', + options: { + watch: false, + }, }, ], '*.{tsx,ts,js,mjs}': { @@ -122,9 +125,12 @@ describe('DevupUINextPlugin', () => { condition: { not: { path: new RegExp( - `node_modules(?!.*(${['@devup-ui'] + `(node_modules(?!.*(${['@devup-ui'] .join('|') - .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$))`, + .replaceAll( + '/', + '[\\/\\\\_]', + )})([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)`, ), }, }, @@ -146,15 +152,21 @@ describe('DevupUINextPlugin', () => { './df/devup-ui/*.css': [ { loader: '@devup-ui/next-plugin/css-loader', + options: { + watch: false, + }, }, ], '*.{tsx,ts,js,mjs}': { condition: { not: { path: new RegExp( - `node_modules(?!.*(${['@devup-ui'] + `(node_modules(?!.*(${['@devup-ui'] .join('|') - .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$))`, + .replaceAll( + '/', + '[\\/\\\\_]', + )})([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)`, ), }, }, @@ -208,15 +220,21 @@ describe('DevupUINextPlugin', () => { './df/devup-ui/*.css': [ { loader: '@devup-ui/next-plugin/css-loader', + options: { + watch: false, + }, }, ], '*.{tsx,ts,js,mjs}': { condition: { not: { path: new RegExp( - `node_modules(?!.*(${['@devup-ui'] + `(node_modules(?!.*(${['@devup-ui'] .join('|') - .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$))`, + .replaceAll( + '/', + '[\\/\\\\_]', + )})([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)`, ), }, }, @@ -266,13 +284,12 @@ describe('DevupUINextPlugin', () => { }) expect(preload).toHaveBeenCalledWith( new RegExp( - `node_modules(?!.*(${['@devup-ui'] + `(node_modules(?!.*(${['@devup-ui'] .join('|') - .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$))`, + .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)`, ), '@devup-ui/react', false, - 'theme', expect.any(String), ) }) diff --git a/packages/next-plugin/src/__tests__/preload.test.ts b/packages/next-plugin/src/__tests__/preload.test.ts index 049ffb6c..6552fbdc 100644 --- a/packages/next-plugin/src/__tests__/preload.test.ts +++ b/packages/next-plugin/src/__tests__/preload.test.ts @@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs' import { existsSync } from 'node:fs' import { join } from 'node:path' -import { codeExtract, registerTheme } from '@devup-ui/wasm' +import { codeExtract, getCss } from '@devup-ui/wasm' import { globSync } from 'glob' import { preload } from '../preload' @@ -30,6 +30,7 @@ vi.mock('glob', () => ({ vi.mock('@devup-ui/wasm', () => ({ codeExtract: vi.fn(), registerTheme: vi.fn(), + getCss: vi.fn(), })) describe('preload', () => { @@ -60,10 +61,9 @@ describe('preload', () => { const excludeRegex = /node_modules/ const libPackage = '@devup-ui/react' const singleCss = false - const theme = { colors: { primary: 'blue' } } const cssDir = '/output/css' - preload(excludeRegex, libPackage, singleCss, theme, cssDir) + preload(excludeRegex, libPackage, singleCss, cssDir) expect(globSync).toHaveBeenCalledWith( ['**/*.tsx', '**/*.ts', '**/*.js', '**/*.mjs'], @@ -74,14 +74,6 @@ describe('preload', () => { ) }) - it('should register theme before processing files', () => { - const theme = { colors: { primary: 'blue' } } - - preload(/node_modules/, '@devup-ui/react', false, theme, '/output/css') - - expect(registerTheme).toHaveBeenCalledWith(theme) - }) - it('should process each collected file', () => { const files = ['src/App.tsx', 'src/components/Button.tsx', '.next/page.tsx'] vi.mocked(globSync).mockReturnValue(files) @@ -89,7 +81,7 @@ describe('preload', () => { .mockReturnValueOnce('src/App.tsx') .mockReturnValueOnce('src/components/Button.tsx') .mockReturnValueOnce('.next/page.tsx') - preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css') + preload(/node_modules/, '@devup-ui/react', false, '/output/css') expect(codeExtract).toHaveBeenCalledTimes(2) expect(codeExtract).toHaveBeenCalledWith( @@ -114,7 +106,7 @@ describe('preload', () => { [Symbol.dispose]: vi.fn(), }) - preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css') + preload(/node_modules/, '@devup-ui/react', false, '/output/css') expect(writeFileSync).toHaveBeenCalledWith( join('/output/css', 'styles.css'), @@ -133,10 +125,15 @@ describe('preload', () => { updatedBaseStyle: false, [Symbol.dispose]: vi.fn(), }) + vi.mocked(getCss).mockReturnValue('') - preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css') + preload(/node_modules/, '@devup-ui/react', false, '/output/css') - expect(writeFileSync).not.toHaveBeenCalled() + expect(writeFileSync).toHaveBeenCalledWith( + join('/output/css', 'devup-ui.css'), + '', + 'utf-8', + ) }) it('should handle empty CSS content', () => { @@ -150,7 +147,7 @@ describe('preload', () => { [Symbol.dispose]: vi.fn(), }) - preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css') + preload(/node_modules/, '@devup-ui/react', false, '/output/css') expect(writeFileSync).toHaveBeenCalledWith( join('/output/css', 'styles.css'), @@ -170,7 +167,7 @@ describe('preload', () => { [Symbol.dispose]: vi.fn(), }) - preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css') + preload(/node_modules/, '@devup-ui/react', false, '/output/css') expect(writeFileSync).toHaveBeenCalledWith( join('/output/css', 'styles.css'), @@ -184,7 +181,7 @@ describe('preload', () => { const singleCss = true const cssDir = '/custom/css/dir' - preload(/node_modules/, libPackage, singleCss, {}, cssDir) + preload(/node_modules/, libPackage, singleCss, cssDir) expect(codeExtract).toHaveBeenCalledWith( expect.stringMatching(/App\.tsx$/), @@ -221,9 +218,9 @@ describe('preload', () => { [Symbol.dispose]: vi.fn(), }) - preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css') + preload(/node_modules/, '@devup-ui/react', false, '/output/css') - expect(writeFileSync).toHaveBeenCalledTimes(2) + expect(writeFileSync).toHaveBeenCalledTimes(3) expect(writeFileSync).toHaveBeenCalledWith( join('/output/css', 'app.css'), '.app { margin: 0; }', diff --git a/packages/next-plugin/src/css-loader.ts b/packages/next-plugin/src/css-loader.ts index eecd71b7..0349b2a1 100644 --- a/packages/next-plugin/src/css-loader.ts +++ b/packages/next-plugin/src/css-loader.ts @@ -6,8 +6,23 @@ function getFileNumByFilename(filename: string) { return parseInt(filename.split('devup-ui-')[1].split('.')[0]) } -const devupUICssLoader: RawLoaderDefinitionFunction = function (_, map, meta) { - const fileNum = getFileNumByFilename(this.resourcePath) - this.callback(null, getCss(fileNum, true), map, meta) +export interface DevupUICssLoaderOptions { + // turbo + theme: object + defaultSheet: object + defaultClassMap: object + defaultFileMap: object + watch: boolean } + +const devupUICssLoader: RawLoaderDefinitionFunction = + function (source, map, meta) { + const { watch } = this.getOptions() + this.callback( + null, + !watch ? source : getCss(getFileNumByFilename(this.resourcePath), true), + map, + meta, + ) + } export default devupUICssLoader diff --git a/packages/next-plugin/src/loader.ts b/packages/next-plugin/src/loader.ts index 3ee7077a..bf37bca7 100644 --- a/packages/next-plugin/src/loader.ts +++ b/packages/next-plugin/src/loader.ts @@ -45,29 +45,23 @@ const devupUILoader: RawLoaderDefinitionFunction = defaultFileMap, defaultSheet, } = this.getOptions() - const callback = this.async() - const id = this.resourcePath if (!init) { init = true - if (defaultFileMap) importFileMap(defaultFileMap) - if (defaultClassMap) importClassMap(defaultClassMap) - if (defaultSheet) importSheet(defaultSheet) - if (theme) registerTheme(theme) + importFileMap(defaultFileMap) + importClassMap(defaultClassMap) + importSheet(defaultSheet) + registerTheme(theme) } + const callback = this.async() try { + const id = this.resourcePath let relCssDir = relative(dirname(id), cssDir).replaceAll('\\', '/') const relativePath = relative(process.cwd(), id) if (!relCssDir.startsWith('./')) relCssDir = `./${relCssDir}` - const { - code, - css = '', - map, - cssFile, - updatedBaseStyle, - } = codeExtract( + const { code, map, cssFile, updatedBaseStyle } = codeExtract( relativePath, source.toString(), libPackage, @@ -78,34 +72,27 @@ const devupUILoader: RawLoaderDefinitionFunction = ) const sourceMap = map ? JSON.parse(map) : null const promises: Promise[] = [] - if (updatedBaseStyle) { + if (updatedBaseStyle && watch) { // update base style promises.push( writeFile(join(cssDir, 'devup-ui.css'), getCss(null, false), 'utf-8'), ) } - if (cssFile) { - const content = `${this.resourcePath} ${Date.now()}` - // should be reset css + if (cssFile && watch) { + // don't write file when build promises.push( writeFile( join(cssDir, basename(cssFile!)), - watch ? `/* ${content} */` : css, + `/* ${this.resourcePath} ${Date.now()} */`, ), + writeFile(sheetFile, exportSheet()), + writeFile(classMapFile, exportClassMap()), + writeFile(fileMapFile, exportFileMap()), ) - if (watch) { - promises.push( - writeFile(sheetFile, exportSheet()), - writeFile(classMapFile, exportClassMap()), - writeFile(fileMapFile, exportFileMap()), - ) - } - Promise.all(promises) - .catch(console.error) - .finally(() => callback(null, code, sourceMap)) - return } - callback(null, code, sourceMap) + Promise.all(promises) + .catch(console.error) + .finally(() => callback(null, code, sourceMap)) } catch (error) { callback(error as Error) } diff --git a/packages/next-plugin/src/plugin.ts b/packages/next-plugin/src/plugin.ts index ac2215ba..d3819f06 100644 --- a/packages/next-plugin/src/plugin.ts +++ b/packages/next-plugin/src/plugin.ts @@ -79,9 +79,9 @@ export function DevupUI( } // disable turbo parallel const excludeRegex = new RegExp( - `node_modules(?!.*(${['@devup-ui', ...include] + `(node_modules(?!.*(${['@devup-ui', ...include] .join('|') - .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$))`, + .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)`, ) if (process.env.NODE_ENV !== 'production') { @@ -91,13 +91,19 @@ export function DevupUI( process.env.NODE_OPTIONS += ' --inspect-brk' } else { // build - preload(excludeRegex, libPackage, singleCss, theme, cssDir) + preload(excludeRegex, libPackage, singleCss, cssDir) } + const defaultSheet = JSON.parse(exportSheet()) + const defaultClassMap = JSON.parse(exportClassMap()) + const defaultFileMap = JSON.parse(exportFileMap()) const rules: NonNullable = { [`./${relative(process.cwd(), cssDir).replaceAll('\\', '/')}/*.css`]: [ { loader: '@devup-ui/next-plugin/css-loader', + options: { + watch: process.env.NODE_ENV === 'development', + }, }, ], '*.{tsx,ts,js,mjs}': { @@ -110,9 +116,9 @@ export function DevupUI( sheetFile, classMapFile, fileMapFile, - defaultSheet: JSON.parse(exportSheet()), - defaultClassMap: JSON.parse(exportClassMap()), - defaultFileMap: JSON.parse(exportFileMap()), + defaultSheet, + defaultClassMap, + defaultFileMap, watch: process.env.NODE_ENV === 'development', singleCss, // for turbopack, load theme is required on loader diff --git a/packages/next-plugin/src/preload.ts b/packages/next-plugin/src/preload.ts index 78d888cf..0c3e89f2 100644 --- a/packages/next-plugin/src/preload.ts +++ b/packages/next-plugin/src/preload.ts @@ -1,21 +1,20 @@ import { readFileSync, realpathSync, writeFileSync } from 'node:fs' import { basename, join, relative } from 'node:path' -import { codeExtract, registerTheme } from '@devup-ui/wasm' +import { codeExtract, getCss } from '@devup-ui/wasm' import { globSync } from 'glob' - export function preload( excludeRegex: RegExp, libPackage: string, singleCss: boolean, - theme: object, cssDir: string, ) { const collected = globSync(['**/*.tsx', '**/*.ts', '**/*.js', '**/*.mjs'], { follow: true, absolute: true, }) - registerTheme(theme) + // fix multi core build issue + collected.sort() for (const file of collected) { const filePath = relative(process.cwd(), realpathSync(file)) if ( @@ -38,4 +37,5 @@ export function preload( writeFileSync(join(cssDir, basename(cssFile!)), css ?? '', 'utf-8') } } + writeFileSync(join(cssDir, 'devup-ui.css'), getCss(null, false), 'utf-8') } diff --git a/packages/webpack-plugin/src/__tests__/loader.test.ts b/packages/webpack-plugin/src/__tests__/loader.test.ts index 6d3183f6..81ef83da 100644 --- a/packages/webpack-plugin/src/__tests__/loader.test.ts +++ b/packages/webpack-plugin/src/__tests__/loader.test.ts @@ -7,9 +7,6 @@ import { exportFileMap, exportSheet, getCss, - importClassMap, - importFileMap, - importSheet, registerTheme, } from '@devup-ui/wasm' @@ -139,7 +136,9 @@ describe('devupUILoader', () => { false, true, ) - expect(t.async()).toHaveBeenCalledWith(null, 'code', null) + await vi.waitFor(() => { + expect(t.async()).toHaveBeenCalledWith(null, 'code', null) + }) expect(writeFile).not.toHaveBeenCalledWith('cssFile', 'css', { encoding: 'utf-8', }) @@ -257,57 +256,5 @@ describe('devupUILoader', () => { [Symbol.dispose]: vi.fn(), }) devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx') - expect(registerTheme).toHaveBeenCalledWith({ - colors: { - primary: '#000', - }, - }) - }) - - it('should register theme on init', async () => { - const { default: devupUILoader } = await import('../loader') - const t = { - getOptions: () => ({ - package: 'package', - cssDir: 'cssFile', - watch: false, - singleCss: true, - theme: { - colors: { - primary: '#000', - }, - }, - defaultClassMap: { - button: 'button', - }, - defaultFileMap: { - button: 'button', - }, - defaultSheet: { - button: 'button', - }, - }), - async: vi.fn().mockReturnValue(vi.fn()), - resourcePath: 'index.tsx', - addDependency: vi.fn(), - } - devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx') - expect(registerTheme).toHaveBeenCalledTimes(1) - expect(importClassMap).toHaveBeenCalledWith({ - button: 'button', - }) - expect(importFileMap).toHaveBeenCalledWith({ - button: 'button', - }) - expect(importSheet).toHaveBeenCalledWith({ - button: 'button', - }) - - devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx') - - expect(registerTheme).toHaveBeenCalledTimes(1) - expect(importClassMap).toHaveBeenCalledTimes(1) - expect(importFileMap).toHaveBeenCalledTimes(1) - expect(importSheet).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/webpack-plugin/src/__tests__/plugin.test.ts b/packages/webpack-plugin/src/__tests__/plugin.test.ts index ecec9189..7e221ff1 100644 --- a/packages/webpack-plugin/src/__tests__/plugin.test.ts +++ b/packages/webpack-plugin/src/__tests__/plugin.test.ts @@ -142,16 +142,20 @@ describe('devupUIWebpackPlugin', () => { include: [ { input: ['lib'], - output: new RegExp('node_modules(?!.*(@devup-ui|lib)([\\/\\\\.]|$))'), + output: new RegExp( + '(node_modules(?!.*(@devup-ui|lib)([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)', + ), }, { input: [], - output: new RegExp('node_modules(?!.*(@devup-ui)([\\/\\\\.]|$))'), + output: new RegExp( + '(node_modules(?!.*(@devup-ui)([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)', + ), }, { input: ['lib', 'lib2'], output: new RegExp( - 'node_modules(?!.*(@devup-ui|lib|lib2)([\\/\\\\.]|$))', + '(node_modules(?!.*(@devup-ui|lib|lib2)([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)', ), }, ], diff --git a/packages/webpack-plugin/src/loader.ts b/packages/webpack-plugin/src/loader.ts index 3ee7077a..2440f0f1 100644 --- a/packages/webpack-plugin/src/loader.ts +++ b/packages/webpack-plugin/src/loader.ts @@ -7,10 +7,6 @@ import { exportFileMap, exportSheet, getCss, - importClassMap, - importFileMap, - importSheet, - registerTheme, } from '@devup-ui/wasm' import type { RawLoaderDefinitionFunction } from 'webpack' @@ -22,13 +18,7 @@ export interface DevupUILoaderOptions { fileMapFile: string watch: boolean singleCss: boolean - // turbo - theme?: object - defaultSheet: object - defaultClassMap: object - defaultFileMap: object } -let init = false const devupUILoader: RawLoaderDefinitionFunction = function (source) { @@ -40,20 +30,9 @@ const devupUILoader: RawLoaderDefinitionFunction = classMapFile, fileMapFile, singleCss, - theme, - defaultClassMap, - defaultFileMap, - defaultSheet, } = this.getOptions() const callback = this.async() const id = this.resourcePath - if (!init) { - init = true - if (defaultFileMap) importFileMap(defaultFileMap) - if (defaultClassMap) importClassMap(defaultClassMap) - if (defaultSheet) importSheet(defaultSheet) - if (theme) registerTheme(theme) - } try { let relCssDir = relative(dirname(id), cssDir).replaceAll('\\', '/') @@ -100,12 +79,10 @@ const devupUILoader: RawLoaderDefinitionFunction = writeFile(fileMapFile, exportFileMap()), ) } - Promise.all(promises) - .catch(console.error) - .finally(() => callback(null, code, sourceMap)) - return } - callback(null, code, sourceMap) + Promise.all(promises) + .catch(console.error) + .finally(() => callback(null, code, sourceMap)) } catch (error) { callback(error as Error) } diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts index a48bc031..a4d54c95 100644 --- a/packages/webpack-plugin/src/plugin.ts +++ b/packages/webpack-plugin/src/plugin.ts @@ -165,9 +165,9 @@ export class DevupUIWebpackPlugin { { test: /\.(tsx|ts|js|mjs|jsx)$/, exclude: new RegExp( - `node_modules(?!.*(${['@devup-ui', ...this.options.include] + `(node_modules(?!.*(${['@devup-ui', ...this.options.include] .join('|') - .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$))`, + .replaceAll('/', '[\\/\\\\_]')})([\\/\\\\.]|$)))|(.mdx.[tj]sx?$)`, ), enforce: 'pre', use: [