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

Add support for removing React properties. #31606

Merged
merged 7 commits into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions examples/react-remove-properties/.gitignore
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions examples/react-remove-properties/README.md
Original file line number Diff line number Diff line change
@@ -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)).
9 changes: 9 additions & 0 deletions examples/react-remove-properties/next.config.js
Original file line number Diff line number Diff line change
@@ -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$'] },
},
}
13 changes: 13 additions & 0 deletions examples/react-remove-properties/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
9 changes: 9 additions & 0 deletions examples/react-remove-properties/pages/index.js
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions packages/next-swc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/next-swc/crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ swc_ecmascript = { version = "0.87.0", 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.0"
Expand Down
11 changes: 10 additions & 1 deletion packages/next-swc/crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ use swc_ecmascript::{
};

pub mod amp_attributes;
pub mod disallow_re_export_all_in_page;
mod auto_cjs;
pub mod disallow_re_export_all_in_page;
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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()),
},
)
}

Expand Down
72 changes: 72 additions & 0 deletions packages/next-swc/crates/core/src/react_remove_properties.rs
Original file line number Diff line number Diff line change
@@ -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 {
losfair marked this conversation as resolved.
Show resolved Hide resolved
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
}
31 changes: 30 additions & 1 deletion packages/next-swc/crates/core/tests/fixture.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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,
);
}
Original file line number Diff line number Diff line change
@@ -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>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function Home() {
return <div data-test-id="1">

<div >

<h1 data-testid="3">Hello World!</h1>

</div>

</div>;
};
Original file line number Diff line number Diff line change
@@ -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>
}
Original file line number Diff line number Diff line change
@@ -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>;
};
1 change: 1 addition & 0 deletions packages/next-swc/crates/core/tests/full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions packages/next/build/swc/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function getBaseSWCOptions({
}
: null,
removeConsole: nextConfig?.experimental?.removeConsole,
reactRemoveProperties: nextConfig?.experimental?.reactRemoveProperties,
}
}

Expand Down
5 changes: 5 additions & 0 deletions packages/next/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ export type NextConfig = { [key: string]: any } & {
| {
exclude?: string[]
}
reactRemoveProperties?:
| boolean
| {
properties?: string[]
}
styledComponents?: boolean
swcMinify?: boolean
cpus?: number
Expand Down