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

Major: use swc-loader for TypeScript compilation #777

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@google-cloud/firestore": "^4.14.0",
"@sentry/node": "^6.10.0",
"@slack/web-api": "^6.3.0",
"@swc/wasm": "^1.2.89",
"@tensorflow/tfjs-node": "^3.8.0",
"@vercel/webpack-asset-relocator-loader": "1.6.0",
"analytics-node": "^5.0.0",
Expand Down Expand Up @@ -63,6 +64,7 @@
"isomorphic-unfetch": "^3.0.0",
"jest": "^27.0.6",
"jimp": "^0.16.1",
"json5": "^2.2.0",
"jugglingdb": "2.0.1",
"koa": "^2.6.2",
"leveldown": "^6.0.0",
Expand All @@ -72,6 +74,7 @@
"mailgun": "^0.5.0",
"mariadb": "^2.0.1-beta",
"memcached": "^2.2.2",
"memory-fs": "^0.5.0",
"mkdirp": "^1.0.4",
"mongoose": "^5.3.12",
"mysql": "^2.16.0",
Expand All @@ -97,15 +100,13 @@
"socket.io": "^4.1.3",
"source-map-support": "^0.5.9",
"stripe": "^8.167.0",
"swc-loader": "^0.1.15",
"swig": "^1.4.2",
"terser": "^5.6.1",
"the-answer": "^1.0.0",
"tiny-json-http": "^7.0.2",
"ts-loader": "^8.3.0",
"tsconfig-paths": "^3.7.0",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"twilio": "^3.23.2",
"typescript": "^4.4.2",
"typescript": "^4.4.3",
"vm2": "^3.6.6",
"vue": "^2.5.17",
"vue-server-renderer": "^2.5.17",
Expand Down
17 changes: 9 additions & 8 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,23 @@ async function main() {
);
checkUnknownAssets('shebang-loader', Object.keys(shebangLoaderAssets));

const { code: tsLoader, assets: tsLoaderAssets } = await ncc(
__dirname + "/../src/loaders/ts-loader",
const { code: swcLoader, assets: swcLoaderAssets } = await ncc(
__dirname + "/../src/loaders/swc-loader",
{
filename: "ts-loader.js",
filename: "swc-loader.js",
minify,
cache,
v8cache,
noAssetBuilds: true
},
);
checkUnknownAssets('ts-loader', Object.keys(tsLoaderAssets).filter(asset => !asset.startsWith('lib/') && !asset.startsWith('typescript/lib')));
checkUnknownAssets('swc-loader', Object.keys(swcLoaderAssets).filter(asset => asset !== 'wasm_bg.wasm'));

const { code: stringifyLoader, assets: stringifyLoaderAssets } = await ncc(
__dirname + "/../src/loaders/stringify-loader",
{ filename: "stringify-loader.js", minify, cache, v8cache }
);
checkUnknownAssets('stringify-loader', Object.keys(stringifyLoader));
checkUnknownAssets('stringify-loader', Object.keys(stringifyLoaderAssets));

const { code: sourcemapSupport, assets: sourcemapAssets } = await ncc(
require.resolve("source-map-support/register"),
Expand All @@ -92,15 +92,16 @@ async function main() {
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache", sourcemapAssets["sourcemap-register.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache", relocateLoaderAssets["relocate-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache", shebangLoaderAssets["shebang-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache", tsLoaderAssets["ts-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/swc-loader.js.cache", swcLoaderAssets["swc-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/wasm_bg.wasm", swcLoaderAssets["wasm_bg.wasm"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/stringify-loader.js.cache", stringifyLoaderAssets["stringify-loader.js.cache"].source);

writeFileSync(__dirname + "/../dist/ncc/cli.js.cache.js", cliAssets["cli.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/index.js.cache.js", indexAssets["index.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache.js", sourcemapAssets["sourcemap-register.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache.js", relocateLoaderAssets["relocate-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache.js", shebangLoaderAssets["shebang-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache.js", tsLoaderAssets["ts-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/swc-loader.js.cache.js", swcLoaderAssets["swc-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/stringify-loader.js.cache.js", stringifyLoaderAssets["stringify-loader.js.cache.js"].source);

writeFileSync(__dirname + "/../dist/ncc/cli.js", cli, { mode: 0o777 });
Expand All @@ -109,7 +110,7 @@ async function main() {
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js", sourcemapSupport);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js", relocateLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js", shebangLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js", tsLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/swc-loader.js", swcLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/stringify-loader.js", stringifyLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/uncacheable.js", readFileSync(__dirname + "/../src/loaders/uncacheable.js"));
writeFileSync(__dirname + "/../dist/ncc/loaders/empty-loader.js", readFileSync(__dirname + "/../src/loaders/empty-loader.js"));
Expand Down
78 changes: 41 additions & 37 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ const { join, dirname, extname, relative, resolve: pathResolve } = require("path
const webpack = require("webpack");
const MemoryFS = require("memory-fs");
const terser = require("terser");
const tsconfigPaths = require("tsconfig-paths");
const { loadTsconfig } = require("tsconfig-paths/lib/tsconfig-loader");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const JSON5 = require("json5");
const shebangRegEx = require('./utils/shebang');
const nccCacheDir = require("./utils/ncc-cache-dir");
const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin;
const { LicenseWebpackPlugin } = require('license-webpack-plugin');
const { version: nccVersion } = require('../package.json');
const { hasTypeModule } = require('./utils/has-type-module');

Expand Down Expand Up @@ -105,31 +103,23 @@ function ncc (
existingAssetNames.push(`${filename}.cache`);
existingAssetNames.push(`${filename}.cache${ext}`);
}
const resolvePlugins = [];
// add TsconfigPathsPlugin to support `paths` resolution in tsconfig
// we need to catch here because the plugin will
// error if there's no tsconfig in the working directory
let fullTsconfig = {};

let tsconfig = {};
try {
const configFileAbsolutePath = walkParentDirs({
const configPath = walkParentDirs({
base: process.cwd(),
start: dirname(entry),
filename: 'tsconfig.json',
});
fullTsconfig = loadTsconfig(configFileAbsolutePath) || {
compilerOptions: {}
};

const tsconfigPathsOptions = { silent: true }
if (fullTsconfig.compilerOptions.allowJs) {
tsconfigPathsOptions.extensions = SUPPORTED_EXTENSIONS
}
resolvePlugins.push(new TsconfigPathsPlugin(tsconfigPathsOptions));

if (tsconfig.resultType === "success") {
tsconfigMatchPath = tsconfigPaths.createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
}
const contents = fs.readFileSync(configPath, 'utf8')
tsconfig = JSON5.parse(contents);
const baseUrl = tsconfig.compilerOptions.baseUrl;
resolveModules.push(pathResolve(dirname(configPath), baseUrl));
} catch (e) {}
const resolvePlugins = [];
const resolveModules = [];
const compilerOptions = tsconfig.compilerOptions || {};


resolvePlugins.push({
apply(resolver) {
Expand Down Expand Up @@ -294,6 +284,7 @@ function ncc (
// webpack defaults to `module` and `main`, but that's
// not really what node.js supports, so we reset it
mainFields: ["main"],
modules: resolveModules.length > 0 ? resolveModules : undefined,
plugins: resolvePlugins
},
// https://github.com/vercel/ncc/pull/29#pullrequestreview-177152175
Expand Down Expand Up @@ -333,17 +324,30 @@ function ncc (
loader: eval('__dirname + "/loaders/uncacheable.js"')
},
{
loader: eval('__dirname + "/loaders/ts-loader.js"'),
loader: eval('__dirname + "/loaders/swc-loader.js"'),
options: {
transpileOnly,
compiler: eval('__dirname + "/typescript.js"'),
compilerOptions: {
module: 'esnext',
target: 'esnext',
...fullTsconfig.compilerOptions,
allowSyntheticDefaultImports: true,
noEmit: false,
outDir: '//'
minify: false, // TODO: maybe we could omit terser if `true`?
exclude: tsconfig.exclude,
sourceMaps: compilerOptions.sourceMap || false,
module: {
type: compilerOptions.module && compilerOptions.module.toLowerCase() === 'commonjs' ? 'commonjs' : 'es6',
strict: false,
strictMode: true,
lazy: false,
noInterop: !compilerOptions.esModuleInterop
},
jsc: {
externalHelpers: false,
keepClassNames: true,
target: compilerOptions.target && compilerOptions.target.toLowerCase() || 'es2021',
paths: compilerOptions.paths,
baseUrl: compilerOptions.baseUrl,
Copy link
Member Author

@styfle styfle Sep 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kdy1 The paths and baseUrl tests are failing with:

panicked at 'failed to get current directory: Error { kind: Unsupported, message: "operation not supported on this platform" }', /home/runner/work/swc/swc/ecmascript/transforms/module/src/path.rs:81:56

Stack:

Error: 
    at u.exports.__wbg_new_59cb74e423758ede (dist/ncc/loaders/swc-loader.js.cache.js:1:2692)
    at null.<anonymous> (wasm://wasm/04480186:0:15193164)
    at null.<anonymous> (wasm://wasm/04480186:0:16752724)
    at null.<anonymous> (wasm://wasm/04480186:0:16566604)
    at null.<anonymous> (wasm://wasm/04480186:0:16677644)
    at null.<anonymous> (wasm://wasm/04480186:0:16749249)
    at null.<anonymous> (wasm://wasm/04480186:0:16736138)
    at null.<anonymous> (wasm://wasm/04480186:0:16687572)
    at null.<anonymous> (wasm://wasm/04480186:0:5322716)
    at null.<anonymous> (wasm://wasm/04480186:0:15233495)

Copy link
Member Author

@styfle styfle Sep 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kdy1 The new @swc/wasm no longer panics, but it still fails with very strange behavior.

Input:

https://github.com/vercel/ncc/blob/main/test/unit/tsconfig-paths/input.ts

import module from "@module";

console.log(module);

Output:

/******/ 	var __webpack_modules__ = ({

/***/ 105:
/***/ ((module) => {

module.exports = eval("require")("../../../../../../../../.././module");


/***/ })

/******/ 	});

Runtime result:

Error: Cannot find module '../../../../../../../../.././module'

parser: {
syntax: 'typescript',
tsx: true, // TODO: use tsconfig.compilerOptions.jsx ???
decorators: compilerOptions.experimentalDecorators || false,
dynamicImport: true, // TODO: use module ???
}
}
}
}]
Expand Down Expand Up @@ -431,7 +435,7 @@ function ncc (

async function finalizeHandler (stats) {
const assets = Object.create(null);
getFlatFiles(mfs.data, assets, relocateLoader.getAssetMeta, fullTsconfig);
getFlatFiles(mfs.data, assets, relocateLoader.getAssetMeta, compilerOptions);
// filter symlinks to existing assets
const symlinks = Object.create(null);
for (const [key, value] of Object.entries(relocateLoader.getSymlinks())) {
Expand Down Expand Up @@ -625,17 +629,17 @@ function ncc (
}

// this could be rewritten with actual FS apis / globs, but this is simpler
function getFlatFiles(mfsData, output, getAssetMeta, tsconfig, curBase = "") {
function getFlatFiles(mfsData, output, getAssetMeta, compilerOptions, curBase = "") {
for (const path of Object.keys(mfsData)) {
const item = mfsData[path];
let curPath = `${curBase}/${path}`;
// directory
if (item[""] === true) getFlatFiles(item, output, getAssetMeta, tsconfig, curPath);
if (item[""] === true) getFlatFiles(item, output, getAssetMeta, compilerOptions, curPath);
// file
else if (!curPath.endsWith("/")) {
const meta = getAssetMeta(curPath.substr(1)) || {};
if(curPath.endsWith(".d.ts")) {
const outDir = tsconfig.compilerOptions.outDir ? pathResolve(tsconfig.compilerOptions.outDir) : pathResolve('dist');
const outDir = compilerOptions.outDir ? pathResolve(compilerOptions.outDir) : pathResolve('dist');
curPath = curPath
.replace(outDir, "")
.replace(process.cwd(), "")
Expand Down
94 changes: 94 additions & 0 deletions src/loaders/swc-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Source code from https://github.com/swc-project/swc-loader/blob/master/src/index.js
// with the following changes:
// - swapped out `@swc/core` for `@swc/wasm`
// - swapped out `swc.transform()` for `swc.transformSync()`

const loaderUtils = require("loader-utils");
const swc = require("@swc/wasm");

function makeLoader() {
return function (source, inputSourceMap) {
// Make the loader async
const callback = this.async();
const filename = this.resourcePath;

let loaderOptions = loaderUtils.getOptions(this) || {};

// Standardize on 'sourceMaps' as the key passed through to Webpack, so that
// users may safely use either one alongside our default use of
// 'this.sourceMap' below without getting error about conflicting aliases.
if (
Object.prototype.hasOwnProperty.call(loaderOptions, "sourceMap") &&
!Object.prototype.hasOwnProperty.call(loaderOptions, "sourceMaps")
) {
loaderOptions = Object.assign({}, loaderOptions, {
sourceMaps: loaderOptions.sourceMap,
});
delete loaderOptions.sourceMap;
}

if (inputSourceMap) {
inputSourceMap = JSON.stringify(inputSourceMap);
}

const programmaticOptions = Object.assign({}, loaderOptions, {
filename,
inputSourceMap: inputSourceMap || undefined,

// Set the default sourcemap behavior based on Webpack's mapping flag,
// but allow users to override if they want.
sourceMaps:
loaderOptions.sourceMaps === undefined
? this.sourceMap
: loaderOptions.sourceMaps,

// Ensure that Webpack will get a full absolute path in the sourcemap
// so that it can properly map the module back to its internal cached
// modules.
sourceFileName: filename,
});
if (!programmaticOptions.inputSourceMap) {
delete programmaticOptions.inputSourceMap;
}

const parseMap = programmaticOptions.parseMap;

delete programmaticOptions.parseMap;
delete programmaticOptions.customize;
delete programmaticOptions.cacheDirectory;
delete programmaticOptions.cacheIdentifier;
delete programmaticOptions.cacheCompression;
delete programmaticOptions.metadataSubscribers;

// auto detect development mode
if (this.mode && programmaticOptions.jsc && programmaticOptions.jsc.transform
&& programmaticOptions.jsc.transform.react &&
!Object.prototype.hasOwnProperty.call(programmaticOptions.jsc.transform.react, "development")) {
programmaticOptions.jsc.transform.react.development = this.mode === 'development'
}

if (programmaticOptions.sourceMaps === "inline") {
// Babel has this weird behavior where if you set "inline", we
// inline the sourcemap, and set 'result.map = null'. This results
// in bad behavior from Babel since the maps get put into the code,
// which Webpack does not expect, and because the map we return to
// Webpack is null, which is also bad. To avoid that, we override the
// behavior here so "inline" just behaves like 'true'.
programmaticOptions.sourceMaps = true;
}

try {
const output = swc.transformSync(source, programmaticOptions);
callback(
null,
output.code,
parseMap ? JSON.parse(output.map) : output.map
);
} catch (e) {
callback(e);
}
};
}

module.exports = makeLoader();
module.exports.custom = makeLoader;
24 changes: 0 additions & 24 deletions src/loaders/ts-loader.js

This file was deleted.