mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(packages): add server-only bundles (#1349)
This commit is contained in:
+55
-22
@@ -23,56 +23,86 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/dev/index.d.ts",
|
||||
"development": "./dist/dev/index.js",
|
||||
"default": "./dist/default/index.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/index.js",
|
||||
"default": "./dist/default/index.js"
|
||||
},
|
||||
"default": "./dist/server/index.js"
|
||||
},
|
||||
"./video": {
|
||||
"types": "./dist/dev/presets/video.d.ts",
|
||||
"development": "./dist/dev/presets/video.js",
|
||||
"default": "./dist/default/presets/video.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/presets/video.js",
|
||||
"default": "./dist/default/presets/video.js"
|
||||
},
|
||||
"default": "./dist/server/presets/video.js"
|
||||
},
|
||||
"./video/*.css": "./dist/default/define/video/*.css",
|
||||
"./video/*": {
|
||||
"types": "./dist/dev/define/video/*.d.ts",
|
||||
"development": "./dist/dev/define/video/*.js",
|
||||
"default": "./dist/default/define/video/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/video/*.js",
|
||||
"default": "./dist/default/define/video/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/video/*.js"
|
||||
},
|
||||
"./audio": {
|
||||
"types": "./dist/dev/presets/audio.d.ts",
|
||||
"development": "./dist/dev/presets/audio.js",
|
||||
"default": "./dist/default/presets/audio.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/presets/audio.js",
|
||||
"default": "./dist/default/presets/audio.js"
|
||||
},
|
||||
"default": "./dist/server/presets/audio.js"
|
||||
},
|
||||
"./audio/*.css": "./dist/default/define/audio/*.css",
|
||||
"./audio/*": {
|
||||
"types": "./dist/dev/define/audio/*.d.ts",
|
||||
"development": "./dist/dev/define/audio/*.js",
|
||||
"default": "./dist/default/define/audio/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/audio/*.js",
|
||||
"default": "./dist/default/define/audio/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/audio/*.js"
|
||||
},
|
||||
"./background": {
|
||||
"types": "./dist/dev/presets/background.d.ts",
|
||||
"development": "./dist/dev/presets/background.js",
|
||||
"default": "./dist/default/presets/background.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/presets/background.js",
|
||||
"default": "./dist/default/presets/background.js"
|
||||
},
|
||||
"default": "./dist/server/presets/background.js"
|
||||
},
|
||||
"./background/*.css": "./dist/default/define/background/*.css",
|
||||
"./background/*": {
|
||||
"types": "./dist/dev/define/background/*.d.ts",
|
||||
"development": "./dist/dev/define/background/*.js",
|
||||
"default": "./dist/default/define/background/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/background/*.js",
|
||||
"default": "./dist/default/define/background/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/background/*.js"
|
||||
},
|
||||
"./ui/*": {
|
||||
"types": "./dist/dev/define/ui/*.d.ts",
|
||||
"development": "./dist/dev/define/ui/*.js",
|
||||
"default": "./dist/default/define/ui/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/ui/*.js",
|
||||
"default": "./dist/default/define/ui/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/ui/*.js"
|
||||
},
|
||||
"./feature/*": {
|
||||
"types": "./dist/dev/define/feature/*.d.ts",
|
||||
"development": "./dist/dev/define/feature/*.js",
|
||||
"default": "./dist/default/define/feature/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/feature/*.js",
|
||||
"default": "./dist/default/define/feature/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/feature/*.js"
|
||||
},
|
||||
"./media/*": {
|
||||
"types": "./dist/dev/define/media/*.d.ts",
|
||||
"development": "./dist/dev/define/media/*.js",
|
||||
"default": "./dist/default/define/media/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/media/*.js",
|
||||
"default": "./dist/default/define/media/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/media/*.js"
|
||||
},
|
||||
"./cdn/media/*": {
|
||||
"types": "./cdn/media/*.dev.d.ts",
|
||||
@@ -87,8 +117,11 @@
|
||||
"./*.css": "./dist/default/define/*.css",
|
||||
"./*": {
|
||||
"types": "./dist/dev/define/*.d.ts",
|
||||
"development": "./dist/dev/define/*.js",
|
||||
"default": "./dist/default/define/*.js"
|
||||
"browser": {
|
||||
"development": "./dist/dev/define/*.js",
|
||||
"default": "./dist/default/define/*.js"
|
||||
},
|
||||
"default": "./dist/server/define/*.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -2,8 +2,8 @@ type DefinableElement = CustomElementConstructor & { tagName: string };
|
||||
|
||||
/** Define a custom element only if not already registered. */
|
||||
export function safeDefine(element: DefinableElement): void {
|
||||
const registry = globalThis.customElements;
|
||||
if (!registry || registry.get(element.tagName)) return;
|
||||
if (!__BROWSER__) return;
|
||||
if (customElements.get(element.tagName)) return;
|
||||
|
||||
registry.define(element.tagName, element);
|
||||
customElements.define(element.tagName, element);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { safeDefine } from '../safe-define';
|
||||
|
||||
describe('safeDefine', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('registers a custom element', () => {
|
||||
class TestElement extends HTMLElement {
|
||||
static tagName = 'test-sd-register';
|
||||
@@ -37,14 +33,4 @@ describe('safeDefine', () => {
|
||||
safeDefine(Replacement);
|
||||
expect(customElements.get('test-sd-no-replace')).toBe(Original);
|
||||
});
|
||||
|
||||
it('does nothing when customElements is unavailable', () => {
|
||||
vi.stubGlobal('customElements', undefined);
|
||||
|
||||
class TestElement extends HTMLElement {
|
||||
static tagName = 'test-sd-ssr';
|
||||
}
|
||||
|
||||
expect(() => safeDefine(TestElement)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
describe('SSR-safe define imports', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it('imports video skin without browser-only globals', async () => {
|
||||
vi.stubGlobal('customElements', undefined);
|
||||
vi.stubGlobal('CSSStyleSheet', undefined);
|
||||
|
||||
await expect(import('../video/skin')).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('imports simple-hls-video without customElements', async () => {
|
||||
vi.stubGlobal('customElements', undefined);
|
||||
|
||||
await expect(import('../media/simple-hls-video')).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
Vendored
+1
@@ -1,4 +1,5 @@
|
||||
declare const __DEV__: boolean;
|
||||
declare const __BROWSER__: boolean;
|
||||
|
||||
declare module '*.css' {
|
||||
const content: string;
|
||||
|
||||
@@ -89,6 +89,7 @@ for (const mode of buildModes) {
|
||||
},
|
||||
define: {
|
||||
__DEV__: isProd ? 'false' : 'true',
|
||||
__BROWSER__: 'true',
|
||||
},
|
||||
plugins: [
|
||||
inlineCssPlugin({ skinsDir, minify: isProd }),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { globSync } from 'node:fs';
|
||||
import { globSync, readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { UserConfig } from 'tsdown';
|
||||
@@ -7,12 +7,51 @@ import { copyCssPlugin } from '../../build/plugins/copy-css-plugin.ts';
|
||||
import { inlineCssPlugin } from '../../build/plugins/inline-css-plugin.ts';
|
||||
import { inlineTemplatePlugin } from '../../build/plugins/inline-template-plugin.ts';
|
||||
|
||||
type BuildMode = 'dev' | 'default';
|
||||
type BuildMode = 'dev' | 'default' | 'server';
|
||||
|
||||
const buildModes: BuildMode[] = ['dev', 'default'];
|
||||
const buildModes: BuildMode[] = ['dev', 'default', 'server'];
|
||||
|
||||
const isServer = (mode: BuildMode) => mode === 'server';
|
||||
|
||||
const skinsDir = resolve(dirname(fileURLToPath(import.meta.url)), '../skins/src');
|
||||
|
||||
/** Stub `*.css?inline` imports with empty strings for the server build. */
|
||||
function stubCssInlinePlugin() {
|
||||
return {
|
||||
name: 'stub-css-inline',
|
||||
resolveId(source: string) {
|
||||
if (source.endsWith('.css?inline')) return { id: `\0stub-css:${source}`, external: false };
|
||||
},
|
||||
load(id: string) {
|
||||
if (id.startsWith('\0stub-css:')) return 'export default ""';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub all `define/*` modules for the server build.
|
||||
*
|
||||
* Reads each source file, extracts `export class` names, and emits stub
|
||||
* classes so that preset modules can still re-export them by name.
|
||||
* Everything else (custom element registration, templates, styles) is dropped.
|
||||
*/
|
||||
function stubDefinePlugin() {
|
||||
const defineDir = resolve(dirname(fileURLToPath(import.meta.url)), 'src/define');
|
||||
return {
|
||||
name: 'stub-define',
|
||||
load(id: string) {
|
||||
if (!id.startsWith(defineDir) || id === defineDir) return;
|
||||
|
||||
const source = readFileSync(id, 'utf8');
|
||||
const classNames = [...source.matchAll(/export class (\w+)/g)].map((m) => m[1]);
|
||||
|
||||
if (classNames.length === 0) return 'export {}';
|
||||
|
||||
return classNames.map((name) => `export class ${name} {}`).join('\n');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const defineEntries = Object.fromEntries(
|
||||
globSync('src/define/**/*.ts')
|
||||
.filter((file) => !file.includes('.test.'))
|
||||
@@ -35,32 +74,36 @@ const createConfig = (mode: BuildMode): UserConfig => ({
|
||||
...defineEntries,
|
||||
...presetEntries,
|
||||
},
|
||||
platform: 'browser',
|
||||
platform: isServer(mode) ? 'node' : 'browser',
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
hash: false,
|
||||
unbundle: true,
|
||||
outExtensions: isServer(mode) ? () => ({ js: '.js', dts: '.d.ts' }) : undefined,
|
||||
treeshake: {
|
||||
// The sideEffects field in package.json uses dist paths, but the build
|
||||
// runs against source. Ensure define/* modules (which register custom
|
||||
// elements as a side effect) are never tree-shaken from skin bundles.
|
||||
moduleSideEffects: [{ test: /\/define\//, sideEffects: true }],
|
||||
},
|
||||
noExternal: [/^@videojs\/icons/, /^@videojs\/skins/],
|
||||
noExternal: isServer(mode) ? [] : [/^@videojs\/icons/, /^@videojs\/skins/],
|
||||
alias: {
|
||||
'@': new URL('./src', import.meta.url).pathname,
|
||||
},
|
||||
outDir: `dist/${mode}`,
|
||||
define: {
|
||||
__DEV__: mode === 'dev' ? 'true' : 'false',
|
||||
__DEV__: mode === 'dev' || isServer(mode) ? 'true' : 'false',
|
||||
__BROWSER__: isServer(mode) ? 'false' : 'true',
|
||||
},
|
||||
dts: mode === 'dev',
|
||||
plugins: [
|
||||
copyCssPlugin({ skinsDir, outDir: `dist/${mode}` }),
|
||||
inlineCssPlugin({ skinsDir, minify: mode !== 'dev' }),
|
||||
inlineTemplatePlugin({ minify: mode !== 'dev' }),
|
||||
],
|
||||
plugins: isServer(mode)
|
||||
? [stubCssInlinePlugin(), stubDefinePlugin()]
|
||||
: [
|
||||
copyCssPlugin({ skinsDir, outDir: `dist/${mode}` }),
|
||||
inlineCssPlugin({ skinsDir, minify: mode !== 'dev' }),
|
||||
inlineTemplatePlugin({ minify: mode !== 'dev' }),
|
||||
],
|
||||
});
|
||||
|
||||
export default defineConfig(buildModes.map((mode) => createConfig(mode)));
|
||||
|
||||
@@ -3,10 +3,12 @@ import { defineConfig } from 'vitest/config';
|
||||
export default defineConfig({
|
||||
define: {
|
||||
__DEV__: 'true',
|
||||
__BROWSER__: 'true',
|
||||
},
|
||||
test: {
|
||||
include: ['src/**/*.test.ts'],
|
||||
passWithNoTests: true,
|
||||
onConsoleLog: (log) => !log.includes('Lit is in dev mode'),
|
||||
onConsoleLog: (log: string) => !log.includes('Lit is in dev mode'),
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user