export function createServerCompiler()

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;
}