in packages/ice/src/service/serverCompiler.ts [100:304]
export function createServerCompiler(options: Options) {
const { task, rootDir, command, speedup, server, syntaxFeatures, getRoutesFile } = options;
const externals = task.config?.externals || {};
const sourceMap = task.config?.sourceMap;
const dev = command === 'start';
// Filter empty alias.
const { ignores, alias } = filterAlias(task.config?.alias || {});
const serverCompiler: ServerCompiler = async (customBuildOptions, {
preBundle,
swc,
externalDependencies,
compilationInfo,
redirectImports,
removeOutputs,
runtimeDefineVars = {},
enableEnv = false,
transformEnv = true,
isServer = true,
bundler,
} = {}) => {
let preBundleDepsMetadata: PreBundleDepsMetaData;
let swcOptions = merge({}, {
// Only get the `compilationConfig` from task config.
compilationConfig: getCompilationConfig(),
}, swc);
function getCompilationConfig() {
const customCompilationConfig = task.config?.swcOptions?.compilationConfig || {};
const getConfig = typeof customCompilationConfig === 'function'
? customCompilationConfig
: () => customCompilationConfig;
return (source: string, id: string) => {
return {
...getConfig(source, id),
sourceMaps: !!sourceMap,
};
};
}
const enableSyntaxFeatures = syntaxFeatures && Object.keys(syntaxFeatures).some(key => syntaxFeatures[key]);
const transformPlugins = getCompilerPlugins(rootDir, {
...task.config,
fastRefresh: false,
enableEnv,
polyfill: false,
swcOptions,
redirectImports,
getRoutesFile,
}, 'esbuild', { isServer });
const define = getRuntimeDefination(task.config?.define || {}, runtimeDefineVars, transformEnv);
if (preBundle) {
const plugins = [
// Add assets plugin for pre-bundle in case of third-party library requires assets.
compilationInfo && createAssetsPlugin(compilationInfo, rootDir),
];
if (enableSyntaxFeatures) {
plugins.push(
transformPipePlugin({
plugins: transformPlugins,
}),
);
}
preBundleDepsMetadata = await createPreBundleDepsMetadata({
task,
alias,
ignores,
rootDir,
define,
speedup,
external: Object.keys(externals),
// Pass transformPlugins only if syntaxFeatures is enabled
plugins,
});
}
server.bundler = bundler ?? server.bundler ?? 'esbuild';
const format = customBuildOptions?.format || 'esm';
let buildOptions: esbuild.BuildOptions = {
bundle: true,
format,
target: 'node12.20.0',
alias,
// enable JSX syntax in .js files by default for compatible with migrate project
// while it is not recommended
loader: { '.js': 'jsx' },
jsx: 'automatic',
sourcemap: typeof sourceMap === 'boolean'
// Transform sourceMap for esbuild.
? sourceMap : (sourceMap.includes('inline') ? 'inline' : !!sourceMap),
banner: customBuildOptions.platform === 'node' && server?.format !== 'cjs'
? {
// See https://github.com/evanw/esbuild/issues/1921#issuecomment-1152991694
js: 'import { createRequire } from \'module\';const require = createRequire(import.meta.url);',
}
: undefined,
...customBuildOptions,
absWorkingDir: rootDir,
define,
external: Object.keys(externals),
plugins: [
...(customBuildOptions.plugins || []),
emptyCSSPlugin(),
externalPlugin({
ignores,
externalDependencies: externalDependencies ?? !server.bundle,
format,
externals: server.externals,
}),
server?.ignores && ignorePlugin(server.ignores),
cssModulesPlugin({
extract: false,
generateLocalIdentName: function (name: string, fileName: string) {
// Compatible with webpack css-loader.
return getCSSModuleIdent({
rootDir,
mode: dev ? 'development' : 'production',
fileName,
localName: name,
rule: speedup ? 'native' : 'loader',
localIdentName: task.config.cssModules?.localIdentName,
});
},
}),
compilationInfo && createAssetsPlugin(compilationInfo, rootDir),
transformPipePlugin({
plugins: [
...transformPlugins,
// Plugin transformImportPlugin need after transformPlugins in case of it has onLoad lifecycle.
dev && preBundle && preBundleDepsMetadata && transformImportPlugin(
preBundleDepsMetadata,
path.join(rootDir, task.config.outputDir, SERVER_OUTPUT_DIR),
),
].filter(Boolean),
}),
].filter(Boolean),
};
if (typeof task.config?.server?.buildOptions === 'function') {
buildOptions = task.config.server.buildOptions(buildOptions);
}
const startTime = new Date().getTime();
logger.debug(`[${server.bundler}]`, `start compile for: ${JSON.stringify(buildOptions.entryPoints)}`);
try {
let bundleResult: any;
let context: esbuild.BuildContext;
if (dev) {
context = await esbuild.context(buildOptions);
bundleResult = await context.rebuild();
} else {
switch (server.bundler) {
case 'webpack':
const webpackServerCompiler = new WebpackServerCompiler({
...buildOptions,
externals,
compileIncludes: task.config.compileIncludes,
plugins: [compilationInfo && new VirualAssetPlugin({ compilationInfo, rootDir })],
rootDir,
userServerConfig: server,
runtimeDefineVars,
});
bundleResult = (await webpackServerCompiler.build())?.compilation;
break;
case 'esbuild':
default:
bundleResult = await esbuild.build(buildOptions);
break;
}
}
logger.debug(`[${server.bundler}]`, `time cost: ${new Date().getTime() - startTime}ms`);
const esm = server?.format === 'esm';
const outJSExtension = esm ? '.mjs' : '.cjs';
const serverEntry = path.join(rootDir, task.config.outputDir, SERVER_OUTPUT_DIR, `index${outJSExtension}`);
if (removeOutputs && bundleResult.metafile) {
// build/server/a.mjs -> a.mjs
const currentOutputFiles = Object.keys(bundleResult.metafile.outputs)
.map(output => output.replace(formatPath(`${path.relative(rootDir, buildOptions.outdir)}${path.sep}`), ''));
const allOutputFiles = fg.sync('**', { cwd: buildOptions.outdir });
const outdatedFiles = difference(allOutputFiles, currentOutputFiles);
outdatedFiles.forEach(outdatedFile => fse.removeSync(path.join(buildOptions.outdir, outdatedFile)));
}
return {
...bundleResult,
context,
serverEntry,
};
} catch (error) {
logger.briefError(
'Server compiled with errors.',
`\nEntryPoints: ${JSON.stringify(buildOptions.entryPoints)}`,
`\n${error.message}`,
);
logger.debug(error.stack);
return {
error: error as Error,
};
}
};
return serverCompiler;
}