Add support for removing React properties. (#31606)

This commit is contained in:
Heyang Zhou 2021-11-26 21:45:03 +08:00 committed by GitHub
parent ed1d0241db
commit 809d0155ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 249 additions and 1 deletions

View 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

View file

@ -0,0 +1,27 @@
# React Remove Properties Example
This example shows how to use the `reactRemoveProperties` config option to remove React properties.
## 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/react-remove-properties)
## 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/react-remove-properties&project-name=react-remove-properties&repository-name=react-remove-properties)
## 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 react-remove-properties react-remove-properties-app
# or
yarn create next-app --example react-remove-properties react-remove-properties-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)).

View file

@ -0,0 +1,9 @@
module.exports = {
experimental: {
reactRemoveProperties: true,
// Or, specify a custom list of regular expressions to match properties to remove.
// The regexes defined here are processed in Rust so the syntax is different from
// JavaScript `RegExp`s. See https://docs.rs/regex.
// reactRemoveProperties: { properties: ['^data-custom$'] },
},
}

View 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"
}
}

View file

@ -0,0 +1,9 @@
const Index = () => (
<div data-test-id="1" data-custom="1a">
<div data-custom="2">
<h1 data-testid="3">Hello World!</h1>
</div>
</div>
)
export default Index

View file

@ -822,6 +822,7 @@ dependencies = [
"either",
"fxhash",
"pathdiff",
"regex",
"serde",
"serde_json",
"styled_components",

View file

@ -25,6 +25,7 @@ swc_ecmascript = { version = "0.88.1", features = ["codegen", "minifier", "optim
swc_node_base = "0.5.1"
swc_stylis = "0.28.0"
tracing = {version = "0.1.28", features = ["release_max_level_off"]}
regex = "1.5"
[dev-dependencies]
swc_ecma_transforms_testing = "0.45.1"

View file

@ -52,6 +52,7 @@ pub mod hook_optimizer;
pub mod next_dynamic;
pub mod next_ssg;
pub mod page_config;
pub mod react_remove_properties;
pub mod remove_console;
pub mod styled_jsx;
mod top_level_binding_collector;
@ -82,6 +83,9 @@ pub struct TransformOptions {
#[serde(default)]
pub remove_console: Option<remove_console::Config>,
#[serde(default)]
pub react_remove_properties: Option<react_remove_properties::Config>,
}
pub fn custom_before_pass(file: Arc<SourceFile>, opts: &TransformOptions) -> impl Fold {
@ -115,6 +119,11 @@ pub fn custom_before_pass(file: Arc<SourceFile>, opts: &TransformOptions) -> imp
Either::Left(remove_console::remove_console(config.clone())),
_ => Either::Right(noop()),
},
match &opts.react_remove_properties {
Some(config) if config.truthy() =>
Either::Left(react_remove_properties::remove_properties(config.clone())),
_ => Either::Right(noop()),
},
)
}

View file

@ -0,0 +1,72 @@
use regex::Regex;
use serde::Deserialize;
use swc_ecmascript::ast::*;
use swc_ecmascript::visit::{noop_fold_type, Fold, FoldWith};
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum Config {
All(bool),
WithOptions(Options),
}
impl Config {
pub fn truthy(&self) -> bool {
match self {
Config::All(b) => *b,
Config::WithOptions(_) => true,
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct Options {
#[serde(default)]
pub properties: Vec<String>,
}
struct RemoveProperties {
properties: Vec<Regex>,
}
impl RemoveProperties {
fn should_remove_property(&self, name: &str) -> bool {
self.properties.iter().any(|p| p.is_match(name))
}
}
impl Fold for RemoveProperties {
noop_fold_type!();
fn fold_jsx_opening_element(&mut self, mut el: JSXOpeningElement) -> JSXOpeningElement {
el.attrs.retain(|attr| match attr {
JSXAttrOrSpread::JSXAttr(JSXAttr {
name: JSXAttrName::Ident(ident),
..
}) if self.should_remove_property(ident.sym.as_ref()) => false,
_ => true,
});
el.fold_children_with(self)
}
}
pub fn remove_properties(config: Config) -> impl Fold {
let mut properties: Vec<Regex> = match config {
Config::WithOptions(x) => x
.properties
.iter()
.map(|pattern| {
Regex::new(pattern).unwrap_or_else(|e| {
panic!("error compiling property regex `{}`: {}", pattern, e);
})
})
.collect(),
_ => vec![],
};
if properties.is_empty() {
// Keep the default regex identical to `babel-plugin-react-remove-properties`.
properties.push(Regex::new(r"^data-test").unwrap());
}
let remover = RemoveProperties { properties };
remover
}

View file

@ -1,6 +1,7 @@
use next_swc::{
amp_attributes::amp_attributes, next_dynamic::next_dynamic, next_ssg::next_ssg,
page_config::page_config_test, remove_console::remove_console, styled_jsx::styled_jsx,
page_config::page_config_test, react_remove_properties::remove_properties,
remove_console::remove_console, styled_jsx::styled_jsx,
};
use std::path::PathBuf;
use swc_common::{chain, comments::SingleThreadedComments, FileName, Mark, Span, DUMMY_SP};
@ -130,3 +131,31 @@ fn remove_console_fixture(input: PathBuf) {
&output,
);
}
#[fixture("tests/fixture/react-remove-properties/default/**/input.js")]
fn react_remove_properties_default_fixture(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
test_fixture(
syntax(),
&|_tr| remove_properties(next_swc::react_remove_properties::Config::All(true)),
&input,
&output,
);
}
#[fixture("tests/fixture/react-remove-properties/custom/**/input.js")]
fn react_remove_properties_custom_fixture(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
test_fixture(
syntax(),
&|_tr| {
remove_properties(next_swc::react_remove_properties::Config::WithOptions(
next_swc::react_remove_properties::Options {
properties: vec!["^data-custom$".into()],
},
))
},
&input,
&output,
);
}

View file

@ -0,0 +1,7 @@
export default function Home() {
return <div data-test-id="1" data-custom="1a">
<div data-custom="2">
<h1 data-testid="3">Hello World!</h1>
</div>
</div>
}

View file

@ -0,0 +1,11 @@
export default function Home() {
return <div data-test-id="1">
<div >
<h1 data-testid="3">Hello World!</h1>
</div>
</div>;
};

View file

@ -0,0 +1,7 @@
export default function Home() {
return <div data-test-id="1" data-custom="1a">
<div data-custom="2">
<h1 data-testid="3" nested={() => (<div data-testid="4">nested</div>)}>Hello World!</h1>
</div>
</div>
}

View file

@ -0,0 +1,12 @@
export default function Home() {
return <div data-custom="1a">
<div data-custom="2">
<h1 nested={()=><div >nested</div>
}>Hello World!</h1>
</div>
</div>;
};

View file

@ -58,6 +58,7 @@ fn test(input: &Path, minify: bool) {
is_development: true,
styled_components: Some(assert_json("{}")),
remove_console: None,
react_remove_properties: None,
};
let options = options.patch(&fm);

View file

@ -67,6 +67,7 @@ function getBaseSWCOptions({
}
: null,
removeConsole: nextConfig?.experimental?.removeConsole,
reactRemoveProperties: nextConfig?.experimental?.reactRemoveProperties,
}
}

View file

@ -132,6 +132,11 @@ export type NextConfig = { [key: string]: any } & {
| {
exclude?: string[]
}
reactRemoveProperties?:
| boolean
| {
properties?: string[]
}
styledComponents?: boolean
swcMinify?: boolean
cpus?: number