Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v3.8.6] Support conditional export. #83

Merged
merged 2 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .api/public.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module "@cocos/ccbuild" {

Check warning on line 1 in .api/public.d.ts

View workflow job for this annotation

GitHub Actions / test

File ignored by default.
/**
* @group Merged Types
*/
Expand Down Expand Up @@ -297,6 +297,7 @@
* @param context
*/
evaluateModuleOverrides(context: ConfigInterface.Context): Record<string, string>;
evalTest<T>(test: ConfigInterface.Test, context: ConfigInterface.Context): T;
}
export namespace StatsQuery {
export class ConstantManager {
Expand Down Expand Up @@ -333,6 +334,10 @@
* This is useful when we need to reduce code size.
*/
WASM_SUBPACKAGE: boolean;
/**
* An internal constant to indicate whether we're using 3D modules.
*/
USE_3D: boolean;
}
export interface IPublicFlagConfig {
DEBUG: boolean;
Expand Down
96 changes: 50 additions & 46 deletions modules/build-engine/src/engine-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
import removeDeprecatedFeatures from './rollup-plugins/remove-deprecated-features';
import type { buildEngine } from '../index';
import { externalWasmLoader } from './rollup-plugins/external-wasm-loader';
import { StatsQuery } from '@ccbuild/stats-query';
import { StatsQuery, ConfigInterface } from '@ccbuild/stats-query';
import { filePathToModuleRequest, formatPath } from '@ccbuild/utils';
import { rpNamedChunk } from './rollup-plugins/systemjs-named-register-plugin';
import { getEnumData, rpEnumScanner } from './rollup-plugins/enum-scanner';
import rpTypescript from '@cocos/rollup-plugin-typescript';
import { IMinifierOptions, minifyPrivatePropertiesTransformer } from './ts-plugins/properties-minifier';
import { IWarningPrinterOptions, warningPrinterTransformer } from './ts-plugins/warning-printer';
import { inlineEnumTransformer } from './ts-plugins/inline-enum';
import { exportControllerTransformer } from './ts-plugins/export-controller';

// import babel
import babel = Transformer.core;
Expand Down Expand Up @@ -99,6 +100,7 @@
const flags = options.flags ?? {};

flags.CULL_MESHOPT = !features.includes('meshopt');
flags.USE_3D = features.includes('3d');

const intrinsicFlags = statsQuery.getIntrinsicFlagsOfFeatures(features);
let buildTimeConstants = statsQuery.constantManager.genBuildTimeConstants({
Expand All @@ -115,11 +117,13 @@
// buildTimeConstants['SUPPORT_JIT'] = options.forceJitValue as boolean;
// }

const moduleOverrides = Object.entries(statsQuery.evaluateModuleOverrides({
const context: ConfigInterface.Context = {
mode: options.mode,
platform: options.platform,
buildTimeConstants,
})).reduce((result, [k, v]) => {
};

const moduleOverrides = Object.entries(statsQuery.evaluateModuleOverrides(context)).reduce((result, [k, v]) => {
result[pathUtils.makePathEqualityKey(k)] = v;
return result;
}, {} as Record<string, string>);
Expand Down Expand Up @@ -266,7 +270,7 @@

{
name: '@cocos/ccbuild|module-overrides',
resolveId(source, importer): string | null {

Check warning on line 273 in modules/build-engine/src/engine-js/index.ts

View workflow job for this annotation

GitHub Actions / test

'importer' is defined but never used
if (moduleOverrides[source]) {
return source;
} else {
Expand Down Expand Up @@ -320,55 +324,55 @@
rollupPlugins.push(...rpEnumScannerPlugin);
}

if (mangleProperties || inlineEnum || warnNoConstructorFound || warnThisDotThreshold) {
rollupPlugins.push(rpTypescript({
tsconfig: ps.join(engineRoot, 'tsconfig.json'),
compilerOptions: {
noEmit: false,
target: undefined,
sourceMap: undefined,
outDir: undefined,
module: 'NodeNext',
skipBuiltinTransformers: true,
},
transformers: (program) => {
const tsTransformers: Array<ts.TransformerFactory<ts.SourceFile>> = [];

// The order of ts transformers is important, don't change the order if you don't know what you are doing.
// warningPrinterTransformer should be the first one to avoid 'undefined' parent after minify private properties.
if (warnNoConstructorFound || warnThisDotThreshold) {
const config: IWarningPrinterOptions = {
warnNoConstructorFound,
warnThisDotThreshold,
};

tsTransformers.push(warningPrinterTransformer(program, config));
}
rollupPlugins.push(rpTypescript({
tsconfig: ps.join(engineRoot, 'tsconfig.json'),
compilerOptions: {
noEmit: false,
target: undefined,
sourceMap: undefined,
outDir: undefined,
module: 'NodeNext',
skipBuiltinTransformers: true,
},
transformers: (program) => {
const tsTransformers: Array<ts.TransformerFactory<ts.SourceFile>> = [];

// The order of ts transformers is important, don't change the order if you don't know what you are doing.
// warningPrinterTransformer should be the first one to avoid 'undefined' parent after minify private properties.
if (warnNoConstructorFound || warnThisDotThreshold) {
const config: IWarningPrinterOptions = {
warnNoConstructorFound,
warnThisDotThreshold,
};

if (inlineEnum) {
const enumData = getEnumData();
if (enumData) {
tsTransformers.push(inlineEnumTransformer(program, enumData));
} else {
console.error(`Enum data is not available for inline enum.`);
}
}
tsTransformers.push(warningPrinterTransformer(program, config));
}

if (mangleProperties) {
const config: Partial<IMinifierOptions> = {};
if (typeof mangleProperties === 'object') {
Object.assign(config, mangleProperties);
}
tsTransformers.push(exportControllerTransformer(program, { context, statsQuery }));

tsTransformers.push(minifyPrivatePropertiesTransformer(program, config));
if (inlineEnum) {
const enumData = getEnumData();
if (enumData) {
tsTransformers.push(inlineEnumTransformer(program, enumData));
} else {
console.error(`Enum data is not available for inline enum.`);
}
}

return {
before: tsTransformers,
};
if (mangleProperties) {
const config: Partial<IMinifierOptions> = {};
if (typeof mangleProperties === 'object') {
Object.assign(config, mangleProperties);
}

tsTransformers.push(minifyPrivatePropertiesTransformer(program, config));
}
}));
}

return {
before: tsTransformers,
};
}
}));

rollupPlugins.push(
rpBabel({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// This code was modified from https://github.com/timocov/ts-transformer-minify-privates

import * as ts from '@cocos/typescript';
import { StatsQuery, ConfigInterface } from '@ccbuild/stats-query';

const EXPORT_CONTROL_JSDOC_TAG_NAME = 'export_if';

export interface IExportControllerOptions {
statsQuery: StatsQuery;
context: ConfigInterface.Context;
}

export class ExportController {
private readonly _context: ts.TransformationContext;
private _currentProgram: ts.Program | null = null;
private _currentSourceFile: ts.SourceFile | null = null;
private _typeChecker!: ts.TypeChecker;
private _options: IExportControllerOptions;

constructor(context: ts.TransformationContext, options: IExportControllerOptions) {
this._context = context;
this._options = { ...options };
}

public visitSourceFile(node: ts.SourceFile, program: ts.Program, context: ts.TransformationContext): ts.SourceFile {
this._currentProgram = program;
this._currentSourceFile = node;
this._typeChecker = program.getTypeChecker();
const result = this.visitNodeAndChildren(node, program, context);
this._currentProgram = null;
this._currentSourceFile = null;
return result;
}

private visitNodeAndChildren(node: ts.SourceFile, program: ts.Program, context: ts.TransformationContext): ts.SourceFile;
private visitNodeAndChildren(node: ts.Node, program: ts.Program, context: ts.TransformationContext): ts.Node;
private visitNodeAndChildren(node: ts.Node, program: ts.Program, context: ts.TransformationContext): ts.Node {
return ts.visitEachChild(
this.visitNode(node, program),
(childNode: ts.Node) => this.visitNodeAndChildren(childNode, program, context),
context
);
}

private visitNode(node: ts.Node, program: ts.Program): ts.Node {

Check warning on line 45 in modules/build-engine/src/engine-js/ts-plugins/export-controller/index.ts

View workflow job for this annotation

GitHub Actions / test

'program' is defined but never used
if (node.kind === ts.SyntaxKind.ExportDeclaration || node.kind == ts.SyntaxKind.VariableStatement) {
const jsDocTags = ts.getJSDocTags(node);
for (const tag of jsDocTags) {
if (tag.tagName.text === EXPORT_CONTROL_JSDOC_TAG_NAME && typeof tag.comment === 'string') {
const testResult = this._options.statsQuery.evalTest(tag.comment, this._options.context);
if (!testResult) {
return this._context.factory.createEmptyStatement(); // ; statement
}
}
}
}
return node;
}
}

export function exportControllerTransformer(program: ts.Program, config: IExportControllerOptions): ts.TransformerFactory<ts.SourceFile> {
return (context: ts.TransformationContext) => {
const controller = new ExportController(context, config);
return (file: ts.SourceFile) => {
return controller.visitSourceFile(file, program, context);
};
};
}
9 changes: 7 additions & 2 deletions modules/stats-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class StatsQuery {
};

this._config.moduleOverrides?.forEach(({ test, overrides, isVirtualModule }) => {
if (this._evalTest(test, context)) {
if (this.evalTest(test, context)) {
addModuleOverrides(overrides, isVirtualModule);
}
});
Expand Down Expand Up @@ -215,7 +215,7 @@ export class StatsQuery {
this.constantManager = new StatsQuery.ConstantManager(engine);
}

private _evalTest<T> (test: Test, context: Context): T {
public evalTest<T> (test: Test, context: Context): T {
// eslint-disable-next-line @typescript-eslint/no-implied-eval,no-new-func
const result = new Function('context', `return ${test}`)(context) as T;
// console.debug(`Eval "${test}" to ${result}`);
Expand Down Expand Up @@ -316,6 +316,11 @@ export namespace StatsQuery {
* This is useful when we need to reduce code size.
*/
WASM_SUBPACKAGE: boolean;

/**
* An internal constant to indicate whether we're using 3D modules.
*/
USE_3D: boolean;
}

export interface IPublicFlagConfig {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cocos/ccbuild",
"version": "2.3.8",
"version": "2.3.9",
"description": "The next generation of build tool for Cocos engine.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
Expand Down
32 changes: 32 additions & 0 deletions test/build-engine/__snapshots__/engine-js.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`engine-js HTML5 conditional export 1`] = `
[
"cc.js",
]
`;

exports[`engine-js HTML5 conditional export 2`] = `
"System.register([], (function (exports) {
'use strict';
return {
execute: (function () {

exports("helloConditionalExport", helloConditionalExport);

var ConditionalExportTest = exports("ConditionalExportTest", function () {
function ConditionalExportTest() {}
var _proto = ConditionalExportTest.prototype;
_proto.helloConditionalExport = function helloConditionalExport() {};
return ConditionalExportTest;
}());
function helloConditionalExport() {}

var conditionalExport = exports("conditionalExport", {
a: 1
});

})
};
}));
"
`;

exports[`engine-js HTML5 mangle private properties 1`] = `
[
"cc.js",
Expand Down
29 changes: 29 additions & 0 deletions test/build-engine/engine-js.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { buildEngine } from '@ccbuild/build-engine';
import * as ps from 'path';
import * as fs from 'fs-extra';

Check warning on line 3 in test/build-engine/engine-js.test.ts

View workflow job for this annotation

GitHub Actions / test

'fs' is defined but never used
import del from 'del';
import { getOutputContent, getOutputDirStructure } from '../utils';

Expand Down Expand Up @@ -436,4 +436,33 @@
expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot();
await del(out, { force: true });
});

test('HTML5 conditional export', async () => {
const out = ps.join(__dirname, './lib-js');

const options: buildEngine.Options = {
engine: ps.join(__dirname, '../test-engine-source'),
out,
platform: 'HTML5',
moduleFormat: 'system',
compress: false,
split: false,
nativeCodeBundleMode: 'wasm',
assetURLFormat: 'runtime-resolved',
noDeprecatedFeatures: false,
sourceMap: false,
features: ['conditional-export-test'],
loose: true,
mode: 'BUILD',
flags: {
DEBUG: false,
WEBGPU: false
}
};

await buildEngine(options);
expect(await getOutputDirStructure(out)).toMatchSnapshot();
expect(await getOutputContent(ps.join(out, 'cc.js'))).toMatchSnapshot();
await del(out, { force: true });
});
});
16 changes: 16 additions & 0 deletions test/dts-bundler/__snapshots__/dts-bundler.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ exports[`bundle dts: cc.d.ts content 1`] = `
export class Prefab {
}
export const A = "A";
/** @export_if context.buildTimeConstants.HTML5 */
export const conditionalExport: {
a: number;
};
/** @export_if !context.buildTimeConstants.HTML5 */
export const conditionalDontExport: {
b: number;
};
export function helloConditionalExport(): void;
export class ConditionalExportTest {
helloConditionalExport(): void;
}
export function helloConditionalExport2(): void;
export class ConditionalExportTest2 {
helloConditionalExport2(): void;
}
export namespace testAsm {
function decodeVertexBuffer(target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string): void;
function decodeIndexBuffer(target: Uint8Array, count: number, size: number, source: Uint8Array): void;
Expand Down
3 changes: 3 additions & 0 deletions test/test-engine-source/cc.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
},
"mangle-private-properties-test": {
"modules": ["mangle-private-properties-test"]
},
"conditional-export-test": {
"modules": ["conditional-export-test"]
}
},
"moduleOverrides": [
Expand Down
6 changes: 6 additions & 0 deletions test/test-engine-source/conditional-export/ccc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

export class ConditionalExportTest {
helloConditionalExport(): void {}
}

export function helloConditionalExport(): void {}
Loading
Loading