SWC import modularization plugin (#34969)
This commit is contained in:
parent
78831c3c84
commit
860c97ccf5
22 changed files with 653 additions and 6 deletions
|
@ -233,6 +233,89 @@ module.exports = {
|
|||
|
||||
If you have feedback about `swcMinify`, please share it on the [feedback discussion](https://github.com/vercel/next.js/discussions/30237).
|
||||
|
||||
### Modularize Imports
|
||||
|
||||
Allows to modularize imports, similar to [babel-plugin-transform-imports](https://www.npmjs.com/package/babel-plugin-transform-imports).
|
||||
|
||||
Transforms member style imports:
|
||||
|
||||
```js
|
||||
import { Row, Grid as MyGrid } from 'react-bootstrap'
|
||||
import { merge } from 'lodash'
|
||||
```
|
||||
|
||||
...into default style imports:
|
||||
|
||||
```js
|
||||
import Row from 'react-bootstrap/lib/Row'
|
||||
import MyGrid from 'react-bootstrap/lib/Grid'
|
||||
import merge from 'lodash/merge'
|
||||
```
|
||||
|
||||
Config for the above transform:
|
||||
|
||||
```js
|
||||
// next.config.js
|
||||
module.exports = {
|
||||
experimental: {
|
||||
modularizeImports: {
|
||||
'react-bootstrap': {
|
||||
transform: 'react-bootstrap/lib/{{member}}',
|
||||
},
|
||||
lodash: {
|
||||
transform: 'lodash/{{member}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Advanced transformations:
|
||||
|
||||
- Using regular expressions
|
||||
|
||||
Similar to `babel-plugin-transform-imports`, but the transform is templated with [handlebars](https://docs.rs/handlebars) and regular expressions are in Rust [regex](https://docs.rs/regex/latest/regex/) crate's syntax.
|
||||
|
||||
The config:
|
||||
|
||||
```js
|
||||
// next.config.js
|
||||
module.exports = {
|
||||
experimental: {
|
||||
modularizeImports: {
|
||||
'my-library/?(((\\w*)?/?)*)': {
|
||||
transform: 'my-library/{{ matches.[1] }}/{{member}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Cause this code:
|
||||
|
||||
```js
|
||||
import { MyModule } from 'my-library'
|
||||
import { App } from 'my-library/components'
|
||||
import { Header, Footer } from 'my-library/components/App'
|
||||
```
|
||||
|
||||
To become:
|
||||
|
||||
```js
|
||||
import MyModule from 'my-library/MyModule'
|
||||
import App from 'my-library/components/App'
|
||||
import Header from 'my-library/components/App/Header'
|
||||
import Footer from 'my-library/components/App/Footer'
|
||||
```
|
||||
|
||||
- Handlebars templating
|
||||
|
||||
This transform uses [handlebars](https://docs.rs/handlebars) to template the replacement import path in the `transform` field. These variables and helper functions are available:
|
||||
|
||||
1. `matches`: Has type `string[]`. All groups matched by the regular expression. `matches.[0]` is the full match.
|
||||
2. `member`: Has type `string`. The name of the member import.
|
||||
3. `lowerCase`, `upperCase`, `camelCase`: Helper functions to convert a string to lower, upper or camel cases.
|
||||
|
||||
## Unsupported Features
|
||||
|
||||
When your application has a `.babelrc` file, Next.js will automatically fall back to using Babel for transforming individual files. This ensures backwards compatibility with existing applications that leverage custom Babel plugins.
|
||||
|
|
34
examples/modularize-imports/.gitignore
vendored
Normal file
34
examples/modularize-imports/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
27
examples/modularize-imports/README.md
Normal file
27
examples/modularize-imports/README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Modularize Imports Example
|
||||
|
||||
This example shows how to use the `modularizeImports` config option.
|
||||
|
||||
## Preview
|
||||
|
||||
Preview the example live on [StackBlitz](http://stackblitz.com/):
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/modularize-imports)
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/modularize-imports&project-name=modularize-imports&repository-name=modularize-imports)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example modularize-imports modularize-imports-app
|
||||
# or
|
||||
yarn create next-app --example modularize-imports modularize-imports-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
|
@ -0,0 +1,3 @@
|
|||
export default function LeftHalf() {
|
||||
return <span>Modularize</span>
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export default function RightHalf() {
|
||||
return <span>Imports</span>
|
||||
}
|
5
examples/modularize-imports/components/halves/index.js
Normal file
5
examples/modularize-imports/components/halves/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// import LeftHalf from './LeftHalf'
|
||||
// import RightHalf from './RightHalf'
|
||||
|
||||
// Remove the exports here so that we can verify that `modularize-imports` is working.
|
||||
// export { LeftHalf, RightHalf };
|
9
examples/modularize-imports/next.config.js
Normal file
9
examples/modularize-imports/next.config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
experimental: {
|
||||
modularizeImports: {
|
||||
'../components/halves': {
|
||||
transform: '../components/halves/{{ member }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
13
examples/modularize-imports/package.json
Normal file
13
examples/modularize-imports/package.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
}
|
||||
}
|
10
examples/modularize-imports/pages/index.js
Normal file
10
examples/modularize-imports/pages/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { LeftHalf, RightHalf } from '../components/halves'
|
||||
|
||||
const Index = () => (
|
||||
<div>
|
||||
<LeftHalf />
|
||||
<RightHalf />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Index
|
158
packages/next-swc/Cargo.lock
generated
158
packages/next-swc/Cargo.lock
generated
|
@ -160,13 +160,34 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
dependencies = [
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -199,6 +220,12 @@ version = "3.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
|
@ -331,7 +358,7 @@ version = "0.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.14.5",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
|
@ -428,13 +455,22 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"block-buffer 0.10.2",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
|
@ -462,6 +498,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.7.0"
|
||||
|
@ -514,6 +556,15 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
|
@ -558,6 +609,20 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "4.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"quick-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
|
@ -775,6 +840,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
|
@ -892,6 +963,7 @@ dependencies = [
|
|||
"easy-error",
|
||||
"either",
|
||||
"fxhash",
|
||||
"handlebars",
|
||||
"once_cell",
|
||||
"pathdiff",
|
||||
"radix_fmt",
|
||||
|
@ -901,6 +973,7 @@ dependencies = [
|
|||
"styled_components",
|
||||
"swc",
|
||||
"swc_atoms",
|
||||
"swc_cached",
|
||||
"swc_common",
|
||||
"swc_css",
|
||||
"swc_ecma_loader",
|
||||
|
@ -1012,6 +1085,12 @@ version = "1.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.0"
|
||||
|
@ -1097,6 +1176,49 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||
dependencies = [
|
||||
"maplit",
|
||||
"pest",
|
||||
"sha-1 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.0"
|
||||
|
@ -1267,6 +1389,12 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
|
@ -1565,6 +1693,18 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.10.0"
|
||||
|
@ -1573,7 +1713,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
|
|||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2274,7 +2414,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"sha-1",
|
||||
"sha-1 0.10.0",
|
||||
"string_enum",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
|
@ -2297,7 +2437,7 @@ dependencies = [
|
|||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha-1",
|
||||
"sha-1 0.10.0",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
"swc_ecma_codegen",
|
||||
|
@ -2692,6 +2832,12 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
|
|
|
@ -28,7 +28,9 @@ swc_ecma_loader = {version = "0.29.0", features = ["node", "lru"]}
|
|||
swc_ecmascript = {version = "0.132.0", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"]}
|
||||
swc_node_base = "0.5.1"
|
||||
swc_stylis = "0.96.1"
|
||||
swc_cached = "0.1.1"
|
||||
tracing = {version = "0.1.28", features = ["release_max_level_off"]}
|
||||
handlebars = "4.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
swc_ecma_transforms_testing = "0.69.0"
|
||||
|
|
|
@ -49,6 +49,7 @@ mod auto_cjs;
|
|||
pub mod disallow_re_export_all_in_page;
|
||||
pub mod emotion;
|
||||
pub mod hook_optimizer;
|
||||
pub mod modularize_imports;
|
||||
pub mod next_dynamic;
|
||||
pub mod next_ssg;
|
||||
pub mod page_config;
|
||||
|
@ -102,6 +103,9 @@ pub struct TransformOptions {
|
|||
|
||||
#[serde(default)]
|
||||
pub emotion: Option<emotion::EmotionOptions>,
|
||||
|
||||
#[serde(default)]
|
||||
pub modularize_imports: Option<modularize_imports::Config>,
|
||||
}
|
||||
|
||||
pub fn custom_before_pass<'a, C: Comments + 'a>(
|
||||
|
@ -191,6 +195,10 @@ pub fn custom_before_pass<'a, C: Comments + 'a>(
|
|||
}
|
||||
})
|
||||
.unwrap_or_else(|| Either::Right(noop())),
|
||||
match &opts.modularize_imports {
|
||||
Some(config) => Either::Left(modularize_imports::modularize_imports(config.clone())),
|
||||
None => Either::Right(noop()),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
235
packages/next-swc/crates/core/src/modularize_imports.rs
Normal file
235
packages/next-swc/crates/core/src/modularize_imports.rs
Normal file
|
@ -0,0 +1,235 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::{Captures, Regex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swc_cached::regex::CachedRegex;
|
||||
use swc_ecmascript::ast::*;
|
||||
use swc_ecmascript::visit::{noop_fold_type, Fold};
|
||||
|
||||
static DUP_SLASH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"//").unwrap());
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Config {
|
||||
pub packages: HashMap<String, PackageConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PackageConfig {
|
||||
pub transform: String,
|
||||
#[serde(default)]
|
||||
pub prevent_full_import: bool,
|
||||
#[serde(default)]
|
||||
pub skip_default_conversion: bool,
|
||||
}
|
||||
|
||||
struct FoldImports {
|
||||
renderer: handlebars::Handlebars<'static>,
|
||||
packages: Vec<(CachedRegex, PackageConfig)>,
|
||||
}
|
||||
|
||||
struct Rewriter<'a> {
|
||||
renderer: &'a handlebars::Handlebars<'static>,
|
||||
key: &'a str,
|
||||
config: &'a PackageConfig,
|
||||
group: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Rewriter<'a> {
|
||||
fn rewrite(&self, old_decl: &ImportDecl) -> Vec<ImportDecl> {
|
||||
if old_decl.type_only || old_decl.asserts.is_some() {
|
||||
return vec![old_decl.clone()];
|
||||
}
|
||||
|
||||
let mut out: Vec<ImportDecl> = Vec::with_capacity(old_decl.specifiers.len());
|
||||
|
||||
for spec in &old_decl.specifiers {
|
||||
match spec {
|
||||
ImportSpecifier::Named(named_spec) => {
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum Data<'a> {
|
||||
Plain(&'a str),
|
||||
Array(&'a [&'a str]),
|
||||
}
|
||||
let mut ctx: HashMap<&str, Data> = HashMap::new();
|
||||
ctx.insert("matches", Data::Array(&self.group[..]));
|
||||
ctx.insert(
|
||||
"member",
|
||||
Data::Plain(
|
||||
named_spec
|
||||
.imported
|
||||
.as_ref()
|
||||
.map(|x| match x {
|
||||
ModuleExportName::Ident(x) => x.as_ref(),
|
||||
ModuleExportName::Str(x) => x.value.as_ref(),
|
||||
})
|
||||
.unwrap_or_else(|| named_spec.local.as_ref()),
|
||||
),
|
||||
);
|
||||
let new_path = self
|
||||
.renderer
|
||||
.render_template(&self.config.transform, &ctx)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("error rendering template for '{}': {}", self.key, e);
|
||||
});
|
||||
let new_path = DUP_SLASH_REGEX.replace_all(&new_path, |_: &Captures| "/");
|
||||
let specifier = if self.config.skip_default_conversion {
|
||||
ImportSpecifier::Named(named_spec.clone())
|
||||
} else {
|
||||
ImportSpecifier::Default(ImportDefaultSpecifier {
|
||||
local: named_spec.local.clone(),
|
||||
span: named_spec.span,
|
||||
})
|
||||
};
|
||||
out.push(ImportDecl {
|
||||
specifiers: vec![specifier],
|
||||
src: Str::from(new_path.as_ref()),
|
||||
span: old_decl.span,
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
if self.config.prevent_full_import {
|
||||
panic!(
|
||||
"import {:?} causes the entire module to be imported",
|
||||
old_decl
|
||||
);
|
||||
} else {
|
||||
// Give up
|
||||
return vec![old_decl.clone()];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl FoldImports {
|
||||
fn should_rewrite<'a>(&'a self, name: &'a str) -> Option<Rewriter<'a>> {
|
||||
for (regex, config) in &self.packages {
|
||||
let group = regex.captures(name);
|
||||
if let Some(group) = group {
|
||||
let group = group
|
||||
.iter()
|
||||
.map(|x| x.map(|x| x.as_str()).unwrap_or_default())
|
||||
.collect::<Vec<&str>>();
|
||||
return Some(Rewriter {
|
||||
renderer: &self.renderer,
|
||||
key: name,
|
||||
config,
|
||||
group,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for FoldImports {
|
||||
noop_fold_type!();
|
||||
fn fold_module(&mut self, mut module: Module) -> Module {
|
||||
let mut new_items: Vec<ModuleItem> = vec![];
|
||||
for item in module.body {
|
||||
match item {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::Import(decl)) => {
|
||||
match self.should_rewrite(&decl.src.value) {
|
||||
Some(rewriter) => {
|
||||
let rewritten = rewriter.rewrite(&decl);
|
||||
new_items.extend(
|
||||
rewritten
|
||||
.into_iter()
|
||||
.map(|x| ModuleItem::ModuleDecl(ModuleDecl::Import(x))),
|
||||
);
|
||||
}
|
||||
None => new_items.push(ModuleItem::ModuleDecl(ModuleDecl::Import(decl))),
|
||||
}
|
||||
}
|
||||
x => {
|
||||
new_items.push(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
module.body = new_items;
|
||||
module
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modularize_imports(config: Config) -> impl Fold {
|
||||
let mut folder = FoldImports {
|
||||
renderer: handlebars::Handlebars::new(),
|
||||
packages: vec![],
|
||||
};
|
||||
folder
|
||||
.renderer
|
||||
.register_helper("lowerCase", Box::new(helper_lower_case));
|
||||
folder
|
||||
.renderer
|
||||
.register_helper("upperCase", Box::new(helper_upper_case));
|
||||
folder
|
||||
.renderer
|
||||
.register_helper("camelCase", Box::new(helper_camel_case));
|
||||
for (mut k, v) in config.packages {
|
||||
// XXX: Should we keep this hack?
|
||||
if !k.starts_with('^') && !k.ends_with('$') {
|
||||
k = format!("^{}$", k);
|
||||
}
|
||||
folder.packages.push((
|
||||
CachedRegex::new(&k).expect("transform-imports: invalid regex"),
|
||||
v,
|
||||
));
|
||||
}
|
||||
folder
|
||||
}
|
||||
|
||||
fn helper_lower_case(
|
||||
h: &Helper<'_, '_>,
|
||||
_: &Handlebars<'_>,
|
||||
_: &Context,
|
||||
_: &mut RenderContext<'_, '_>,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
// get parameter from helper or throw an error
|
||||
let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
|
||||
out.write(param.to_lowercase().as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn helper_upper_case(
|
||||
h: &Helper<'_, '_>,
|
||||
_: &Handlebars<'_>,
|
||||
_: &Context,
|
||||
_: &mut RenderContext<'_, '_>,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
// get parameter from helper or throw an error
|
||||
let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
|
||||
out.write(param.to_uppercase().as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn helper_camel_case(
|
||||
h: &Helper<'_, '_>,
|
||||
_: &Handlebars<'_>,
|
||||
_: &Context,
|
||||
_: &mut RenderContext<'_, '_>,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
// get parameter from helper or throw an error
|
||||
let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
|
||||
let value = if param.is_empty() || param.chars().next().unwrap().is_lowercase() {
|
||||
Cow::Borrowed(param)
|
||||
} else {
|
||||
let mut it = param.chars();
|
||||
let fst = it.next().unwrap();
|
||||
Cow::Owned(fst.to_lowercase().chain(it).collect::<String>())
|
||||
};
|
||||
out.write(value.as_ref())?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use next_swc::{
|
||||
amp_attributes::amp_attributes,
|
||||
emotion::{self, EmotionOptions},
|
||||
modularize_imports::modularize_imports,
|
||||
next_dynamic::next_dynamic,
|
||||
next_ssg::next_ssg,
|
||||
page_config::page_config_test,
|
||||
|
@ -312,3 +313,46 @@ fn next_emotion_fixture(input: PathBuf) {
|
|||
&output,
|
||||
);
|
||||
}
|
||||
|
||||
#[fixture("tests/fixture/modularize-imports/**/input.js")]
|
||||
fn modularize_imports_fixture(input: PathBuf) {
|
||||
use next_swc::modularize_imports::PackageConfig;
|
||||
let output = input.parent().unwrap().join("output.js");
|
||||
test_fixture(
|
||||
syntax(),
|
||||
&|_tr| {
|
||||
modularize_imports(next_swc::modularize_imports::Config {
|
||||
packages: vec![
|
||||
(
|
||||
"react-bootstrap".to_string(),
|
||||
PackageConfig {
|
||||
transform: "react-bootstrap/lib/{{member}}".into(),
|
||||
prevent_full_import: false,
|
||||
skip_default_conversion: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"my-library/?(((\\w*)?/?)*)".to_string(),
|
||||
PackageConfig {
|
||||
transform: "my-library/{{ matches.[1] }}/{{member}}".into(),
|
||||
prevent_full_import: false,
|
||||
skip_default_conversion: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"my-library-2".to_string(),
|
||||
PackageConfig {
|
||||
transform: "my-library-2/{{ camelCase member }}".into(),
|
||||
prevent_full_import: false,
|
||||
skip_default_conversion: true,
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
})
|
||||
},
|
||||
&input,
|
||||
&output,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { MyModule } from 'my-library';
|
||||
import { App } from 'my-library/components';
|
||||
import { Header, Footer } from 'my-library/components/App';
|
|
@ -0,0 +1,4 @@
|
|||
import MyModule from 'my-library/MyModule';
|
||||
import App from 'my-library/components/App';
|
||||
import Header from 'my-library/components/App/Header';
|
||||
import Footer from 'my-library/components/App/Footer';
|
|
@ -0,0 +1,2 @@
|
|||
import { Grid, Row, Col as Col1 } from 'react-bootstrap';
|
||||
import { MyModule, Widget } from 'my-library-2';
|
|
@ -0,0 +1,5 @@
|
|||
import Grid from "react-bootstrap/lib/Grid";
|
||||
import Row from "react-bootstrap/lib/Row";
|
||||
import Col1 from "react-bootstrap/lib/Col";
|
||||
import { MyModule } from 'my-library-2/myModule';
|
||||
import { Widget } from 'my-library-2/widget';
|
|
@ -62,6 +62,7 @@ fn test(input: &Path, minify: bool) {
|
|||
relay: None,
|
||||
shake_exports: None,
|
||||
emotion: Some(assert_json("{}")),
|
||||
modularize_imports: None,
|
||||
};
|
||||
|
||||
let options = options.patch(&fm);
|
||||
|
|
|
@ -102,6 +102,7 @@ function getBaseSWCOptions({
|
|||
: null,
|
||||
removeConsole: nextConfig?.compiler?.removeConsole,
|
||||
reactRemoveProperties: nextConfig?.compiler?.reactRemoveProperties,
|
||||
modularizeImports: nextConfig?.experimental?.modularizeImports,
|
||||
relay: nextConfig?.compiler?.relay,
|
||||
emotion: getEmotionOptions(nextConfig, development),
|
||||
}
|
||||
|
|
|
@ -1653,6 +1653,7 @@ export default async function getBaseWebpackConfig(
|
|||
styledComponents: config.compiler?.styledComponents,
|
||||
relay: config.compiler?.relay,
|
||||
emotion: config.experimental?.emotion,
|
||||
modularizeImports: config.experimental?.modularizeImports,
|
||||
})
|
||||
|
||||
const cache: any = {
|
||||
|
|
|
@ -118,6 +118,14 @@ export interface ExperimentalConfig {
|
|||
autoLabel?: 'dev-only' | 'always' | 'never'
|
||||
labelFormat?: string
|
||||
}
|
||||
modularizeImports?: Record<
|
||||
string,
|
||||
{
|
||||
transform: string
|
||||
preventFullImport?: boolean
|
||||
skipDefaultConversion?: boolean
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue