Skip to content

Commit

Permalink
feat(loader): implement tsconfig compatible loader
Browse files Browse the repository at this point in the history
  • Loading branch information
LongYinan authored and Brooooooklyn committed May 21, 2021
1 parent e89ca14 commit 8c1cd85
Show file tree
Hide file tree
Showing 21 changed files with 668 additions and 90 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

## Support matrix

| | node10 | node12 | node14 | node15 |
| | node10 | node12 | node14 | node16 |
| ------------------- | ------ | ------ | ------ | ------ |
| Windows x64 |||||
| Windows x32 |||||
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"lodash": "^4.17.21",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.0",
"react": "^17.0.2",
"sinon": "^10.0.0",
"typescript": "^4.2.4"
},
Expand Down
42 changes: 42 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# `@swc-node/core`
<a href="https://npmcharts.com/compare/@swc-node/core?minimal=true"><img src="https://img.shields.io/npm/dm/@swc-node/core.svg?sanitize=true" alt="Downloads" /></a>

> 🚀 Help me to become a full-time open-source developer by [sponsoring me on Github](https://github.com/sponsors/Brooooooklyn)
## Benchmark

Expand All @@ -22,6 +25,45 @@ export interface Options {
experimentalDecorators?: boolean
emitDecoratorMetadata?: boolean
dynamicImport?: boolean
esModuleInterop?: boolean
keepClassNames?: boolean
react?: Partial<ReactConfig>
}

export interface ReactConfig {
/**
* Replace the function used when compiling JSX expressions.
*
* Defaults to `React.createElement`.
*/
pragma: string;
/**
* Replace the component used when compiling JSX fragments.
*
* Defaults to `React.Fragment`
*/
pragmaFrag: string;
/**
* Toggles whether or not to throw an error if a XML namespaced tag name is used. For example:
* `<f:image />`
*
* Though the JSX spec allows this, it is disabled by default since React's
* JSX does not currently have support for it.
*
*/
throwIfNamespace: boolean;
/**
* Toggles plugins that aid in development, such as @swc/plugin-transform-react-jsx-self
* and @swc/plugin-transform-react-jsx-source.
*
* Defaults to `false`,
*
*/
development: boolean;
/**
* Use `Object.assign()` instead of `_extends`. Defaults to false.
*/
useBuiltins: boolean;
}

export function transformSync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ Generated by [AVA](https://avajs.dev).
const Button = ({ text })=>/*#__PURE__*/ h("div", null, text)␊
;␊
`

## should transform jsx into new jsx runtime

> Snapshot 1
`"use strict";␊
var _jsxRuntime = require("react/jsx-runtime");␊
const Button = ({ text })=>/*#__PURE__*/ (0, _jsxRuntime).jsx("div", {␊
children: text␊
})␊
;␊
`
Binary file not shown.
12 changes: 12 additions & 0 deletions packages/integrate/__tests__/react-pragma/react-pragma.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ test('should transform jsx factory use React.pragma', async (t) => {
).code,
)
})

test('should transform jsx into new jsx runtime', async (t) => {
t.snapshot(
(
await transform(fixture, 'test.tsx', {
react: {
runtime: 'automatic',
},
})
).code,
)
})
4 changes: 4 additions & 0 deletions packages/jest/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# `@swc-node/jest`

<a href="https://npmcharts.com/compare/@swc-node/jest?minimal=true"><img src="https://img.shields.io/npm/dm/@swc-node/jest.svg?sanitize=true" alt="Downloads" /></a>

> 🚀 Help me to become a full-time open-source developer by [sponsoring me on Github](https://github.com/sponsors/Brooooooklyn)
## Usage

```ts
Expand Down
41 changes: 41 additions & 0 deletions packages/loader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# `@swc-node/loader`

> 🚀 Help me to become a full-time open-source developer by [sponsoring me on Github](https://github.com/sponsors/Brooooooklyn)
<a href="https://npmcharts.com/compare/@swc-node/loader?minimal=true"><img src="https://img.shields.io/npm/dm/@swc-node/loader.svg?sanitize=true" alt="Downloads" /></a>

## Usage

```js
{
test: /\.tsx?$/,
use: [
{
loader: '@swc-node/loader',
// If options not passed
// `@swc-node/loader` will read the project `tsconfig.json` as compile options
// If the default `tsconfig.json` parse failed or not existed
// The default options will be used
// `compilerOptions` is the same with `compilerOptions in tsconfig`
options: {
// if `compilerOptions` provided, `configFile` will be ignored
compilerOptions: {
target: 'ES5',
module: 'esnext',
sourceMap: true,
jsx: true,
},
// absolute path for tsconfig.json
configFile: path.join(process.cwd(), 'tsconfig.build.json'),
// enable react fast refresh
fastRefresh: true
}
}
],
exclude: /node_modules/,
}
```

## Differences between [swc-loader](https://github.com/swc-project/swc-loader)

This `loader` is compatible with `tsconfig.json` and `compilerOptions` in `tsconfig.json`.
1 change: 1 addition & 0 deletions packages/loader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./lib').loader
44 changes: 44 additions & 0 deletions packages/loader/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@swc-node/loader",
"version": "1.0.0",
"description": "Webpack loader powered by swc",
"keywords": [
"swc",
"babel",
"loader",
"ts-loader",
"babel-loader",
"swc-loader",
"napi-rs",
"napi",
"node-api",
"tsc"
],
"author": "LongYinan <longyinan.brooklyn@bytedance.com>",
"homepage": "https://github.com/Brooooooklyn/swc-node",
"license": "MIT",
"main": "./index.js",
"files": ["lib", "LICENSE"],
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"dependencies": {
"@swc-node/core": "^1.4.0",
"@swc-node/register": "^1.2.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Brooooooklyn/swc-node.git"
},
"bugs": {
"url": "https://github.com/Brooooooklyn/swc-node/issues"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"devDependencies": {
"@types/webpack": "^5.28.0"
}
}
31 changes: 31 additions & 0 deletions packages/loader/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { transform } from '@swc-node/core'
import { readDefaultTsConfig, tsCompilerOptionsToSwcConfig } from '@swc-node/register/read-default-tsconfig'
import { CompilerOptions, convertCompilerOptionsFromJson } from 'typescript'

import type { LoaderContext } from 'webpack'

export function loader(
this: LoaderContext<{
compilerOptions?: CompilerOptions
configFile?: string
fastRefresh?: boolean
}>,
source: string,
) {
const callback = this.async()
const { compilerOptions, configFile, fastRefresh } = this.getOptions() ?? {}
const options = convertCompilerOptionsFromJson(compilerOptions, '').options ?? readDefaultTsConfig(configFile)
const swcOptions = tsCompilerOptionsToSwcConfig(options, this.resourcePath)
if (fastRefresh) {
if (swcOptions.react) {
swcOptions.react.refresh = true
} else {
swcOptions.react = {
refresh: true,
}
}
}
transform(source, this.resourcePath, swcOptions)
.then(({ code, map }) => callback(null, code, map))
.catch((err) => callback(err))
}
14 changes: 14 additions & 0 deletions packages/loader/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["."],
"exclude": ["lib"],
"references": [
{ "path": "../core" },
{ "path": "../register" }
]
}
9 changes: 9 additions & 0 deletions packages/loader/tsconfig.project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"include": [],
"files": ["src/index.ts"],
"references": [
{ "path": "../core/tsconfig.project.json" },
{ "path": "../register/tsconfig.project.json" }
]
}
4 changes: 4 additions & 0 deletions packages/register/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# `@swc-node/register`

<a href="https://npmcharts.com/compare/@swc-node/register?minimal=true"><img src="https://img.shields.io/npm/dm/@swc-node/register.svg?sanitize=true" alt="Downloads" /></a>

> 🚀 Help me to become a full-time open-source developer by [sponsoring me on Github](https://github.com/sponsors/Brooooooklyn)
## Usage

```ts
Expand Down
2 changes: 1 addition & 1 deletion packages/register/__test__/create-sourcemap-option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Config } from '@swc/core'
import test from 'ava'
import { CompilerOptions } from 'typescript'

import { createSourcemapOption } from '../register'
import { createSourcemapOption } from '../read-default-tsconfig'

const FIXTURES: [CompilerOptions, Config['sourceMaps']][] = [
[{ sourceMap: true, inlineSourceMap: true }, 'inline'],
Expand Down
8 changes: 8 additions & 0 deletions packages/register/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,13 @@
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"exports": {
".": {
"node": "./index.js"
},
"./read-default-tsconfig": {
"node": "./lib/read-default-tsconfig.js"
}
}
}
84 changes: 80 additions & 4 deletions packages/register/read-default-tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import chalk from 'chalk'
import debugFactory from 'debug'
import * as ts from 'typescript'

const debug = debugFactory('@swc-node')
import type { Options } from '@swc-node/core'

export function readDefaultTsConfig() {
const tsConfigPath =
process.env.SWC_NODE_PROJECT ?? process.env.TS_NODE_PROJECT ?? join(process.cwd(), 'tsconfig.json')
const debug = debugFactory('@swc-node')

export function readDefaultTsConfig(
tsConfigPath = process.env.SWC_NODE_PROJECT ?? process.env.TS_NODE_PROJECT ?? join(process.cwd(), 'tsconfig.json'),
) {
let compilerOptions: Partial<ts.CompilerOptions & { fallbackToTs: (path: string) => boolean }> = {
target: ts.ScriptTarget.ES2018,
module: ts.ModuleKind.CommonJS,
Expand Down Expand Up @@ -39,3 +40,78 @@ export function readDefaultTsConfig() {
}
return compilerOptions
}

function toTsTarget(target: ts.ScriptTarget) {
switch (target) {
case ts.ScriptTarget.ES3:
return 'es3'
case ts.ScriptTarget.ES5:
return 'es5'
case ts.ScriptTarget.ES2015:
return 'es2015'
case ts.ScriptTarget.ES2016:
return 'es2016'
case ts.ScriptTarget.ES2017:
return 'es2017'
case ts.ScriptTarget.ES2018:
return 'es2018'
case ts.ScriptTarget.ES2019:
case ts.ScriptTarget.ES2020:
case ts.ScriptTarget.ESNext:
case ts.ScriptTarget.Latest:
return 'es2019'
case ts.ScriptTarget.JSON:
return 'es5'
}
}

function toModule(moduleKind: ts.ModuleKind) {
switch (moduleKind) {
case ts.ModuleKind.CommonJS:
return 'commonjs'
case ts.ModuleKind.UMD:
return 'umd'
case ts.ModuleKind.AMD:
return 'amd'
case ts.ModuleKind.ES2015:
case ts.ModuleKind.ES2020:
case ts.ModuleKind.ESNext:
case ts.ModuleKind.None:
return 'es6'
case ts.ModuleKind.System:
throw new TypeError('Do not support system kind module')
}
}

export function createSourcemapOption(options: ts.CompilerOptions) {
return options.sourceMap !== false
? options.inlineSourceMap
? 'inline'
: true
: options.inlineSourceMap
? 'inline'
: false
}

export function tsCompilerOptionsToSwcConfig(options: ts.CompilerOptions, filename: string): Options {
return {
target: toTsTarget(options.target ?? ts.ScriptTarget.ES2018),
module: toModule(options.module ?? ts.ModuleKind.ES2015),
sourcemap: createSourcemapOption(options),
jsx: filename.endsWith('.tsx') || filename.endsWith('.jsx') || Boolean(options.jsx),
react:
options.jsxFactory || options.jsxFragmentFactory || options.jsx || options.jsxImportSource
? {
pragma: options.jsxFactory,
pragmaFrag: options.jsxFragmentFactory,
importSource: options.jsxImportSource,
runtime: (options.jsx ?? 0) >= ts.JsxEmit.ReactJSX ? 'automatic' : 'classic',
}
: undefined,
experimentalDecorators: options.experimentalDecorators ?? false,
emitDecoratorMetadata: options.emitDecoratorMetadata ?? false,
dynamicImport: options.module ? options.module >= ts.ModuleKind.ES2020 : true,
esModuleInterop: options.esModuleInterop ?? false,
keepClassNames: true,
}
}
Loading

0 comments on commit 8c1cd85

Please sign in to comment.