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

feat: enable configuration of styled-components transform and enable css prop support #37861

Merged
merged 8 commits into from
Jun 23, 2022
27 changes: 23 additions & 4 deletions docs/advanced-features/compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,31 @@ We're working to port `babel-plugin-styled-components` to the Next.js Compiler.
First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `next.config.js` file:

```js
// next.config.js

module.exports = {
compiler: {
// ssr and displayName are configured by default
styledComponents: true,
// see https://styled-components.com/docs/tooling#babel-plugin for more info on the options.
styledComponents: boolean | {
// Enabled by default in development, disabled in production to reduce file size,
// setting this will override the default for all environments.
displayName?: boolean,
// Enabled by default.
ssr?: boolean,
// Enabled by default.
fileName?: boolean,
// Empty by default.
topLevelImportPaths?: string[],
// Defaults to ["index"].
meaninglessFileNames?: string[],
// Disabled by default.
cssProp?: boolean,
Copy link
Member Author

Choose a reason for hiding this comment

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

Wasn't sure if it would count as a breaking change if this was enabled by default, would probably be best if it was enabled.

// Empty by default.
namespace?: string,
// Not supported yet.
minify?: boolean,
// Not supported yet.
transpileTemplateLiterals?: boolean,
// Not supported yet.
pure?: boolean,
},
}
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -209,5 +209,5 @@
"node": ">=12.22.0",
"pnpm": ">= 7.2.1"
},
"packageManager": "pnpm@7.2.1"
"packageManager": "pnpm@7.3.0"
}
23 changes: 6 additions & 17 deletions packages/next-swc/crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,12 @@ pub fn custom_before_pass<'a, C: Comments + 'a>(
styled_jsx::styled_jsx(cm.clone(), file.name.clone()),
hook_optimizer::hook_optimizer(),
match &opts.styled_components {
Some(config) => {
let config = Rc::new(config.clone());
let state: Rc<RefCell<styled_components::State>> = Default::default();

Either::Left(chain!(
styled_components::analyzer(config.clone(), state.clone()),
styled_components::display_name_and_id(
file.name.clone(),
file.src_hash,
config,
state
)
))
}
None => {
Either::Right(noop())
}
Some(config) => Either::Left(styled_components::styled_components(
file.name.clone(),
file.src_hash,
config.clone(),
)),
None => Either::Right(noop()),
},
Optional::new(
next_ssg::next_ssg(eliminated_packages),
Expand Down
19 changes: 16 additions & 3 deletions packages/next-swc/crates/styled_components/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use crate::{
use serde::Deserialize;
use std::{cell::RefCell, rc::Rc};
use swc_atoms::JsWord;
use swc_common::{chain, FileName};
use swc_common::{chain, pass::Optional, FileName};
use swc_ecmascript::visit::{Fold, VisitMut};

mod css;
Expand All @@ -28,6 +28,9 @@ pub struct Config {
#[serde(default = "true_by_default")]
pub file_name: bool,

#[serde(default = "default_index_file_name")]
pub meaningless_file_names: Vec<String>,

#[serde(default)]
pub namespace: String,

Expand All @@ -40,6 +43,9 @@ pub struct Config {
#[serde(default)]
pub minify: bool,

#[serde(default)]
pub pure: bool,

#[serde(default)]
pub css_prop: bool,
}
Expand All @@ -48,6 +54,10 @@ fn true_by_default() -> bool {
true
}

fn default_index_file_name() -> Vec<String> {
vec!["index".to_string()]
}

impl Config {
pub(crate) fn use_namespace(&self) -> String {
if self.namespace.is_empty() {
Expand All @@ -70,7 +80,10 @@ pub fn styled_components(

chain!(
analyzer(config.clone(), state.clone()),
display_name_and_id(file_name, src_file_hash, config, state),
transpile_css_prop()
Optional {
enabled: config.css_prop,
visitor: transpile_css_prop(state.clone())
},
display_name_and_id(file_name, src_file_hash, config.clone(), state)
)
}
37 changes: 19 additions & 18 deletions packages/next-swc/crates/styled_components/src/utils/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,28 @@ impl Visit for Analyzer<'_> {
fn visit_var_declarator(&mut self, v: &VarDeclarator) {
v.visit_children_with(self);

if let Pat::Ident(name) = &v.name {
if let Some(Expr::Call(CallExpr {
if let (
Pat::Ident(name),
Some(Expr::Call(CallExpr {
callee: Callee::Expr(callee),
args,
..
})) = v.init.as_deref()
{
if let Expr::Ident(callee) = &**callee {
if &*callee.sym == "require" && args.len() == 1 && args[0].spread.is_none() {
if let Expr::Lit(Lit::Str(v)) = &*args[0].expr {
let is_styled = if self.config.top_level_import_paths.is_empty() {
&*v.value == "styled-components"
|| v.value.starts_with("styled-components/")
} else {
self.config.top_level_import_paths.contains(&v.value)
};

if is_styled {
self.state.styled_required = Some(name.id.to_id());
self.state.unresolved_ctxt = Some(callee.span.ctxt);
}
})),
) = (&v.name, v.init.as_deref())
{
if let Expr::Ident(callee) = &**callee {
if &*callee.sym == "require" && args.len() == 1 && args[0].spread.is_none() {
if let Expr::Lit(Lit::Str(v)) = &*args[0].expr {
let is_styled = if self.config.top_level_import_paths.is_empty() {
&*v.value == "styled-components"
|| v.value.starts_with("styled-components/")
} else {
self.config.top_level_import_paths.contains(&v.value)
};

if is_styled {
self.state.styled_required = Some(name.id.to_id());
self.state.unresolved_ctxt = Some(callee.span.ctxt);
}
}
}
Expand Down
23 changes: 14 additions & 9 deletions packages/next-swc/crates/styled_components/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
pub use self::analyzer::{analyze, analyzer};
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use std::{borrow::Cow, cell::RefCell};
use swc_atoms::js_word;
use swc_common::{collections::AHashMap, SyntaxContext};
Expand Down Expand Up @@ -210,7 +208,11 @@ impl State {
false
}

fn import_local_name(&self, name: &str, cache_identifier: Option<&Ident>) -> Option<Id> {
pub(crate) fn import_local_name(
&self,
name: &str,
cache_identifier: Option<&Ident>,
) -> Option<Id> {
if name == "default" {
if let Some(cached) = self.imported_local_name.clone() {
return Some(cached);
Expand Down Expand Up @@ -251,6 +253,10 @@ impl State {
name
}

pub(crate) fn set_import_name(&mut self, id: Id) {
self.imported_local_name = Some(id);
}

fn is_helper(&self, e: &Expr) -> bool {
self.is_create_global_style_helper(e)
|| self.is_css_helper(e)
Expand Down Expand Up @@ -304,10 +310,9 @@ impl State {
}

pub fn prefix_leading_digit(s: &str) -> Cow<str> {
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\d)").unwrap());
Copy link
Member Author

Choose a reason for hiding this comment

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

regex here was overkill


REGEX.replace(s, |s: &Captures| {
//
format!("sc-{}", s.get(0).unwrap().as_str())
})
if s.chars().next().map(|c| c.is_digit(10)).unwrap_or(false) {
Cow::Owned(format!("sc-{}", s))
} else {
Cow::Borrowed(s)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,21 @@ struct DisplayNameAndId {

impl DisplayNameAndId {
fn get_block_name(&self, p: &Path) -> String {
let file_stem = p.file_stem();
if let Some(file_stem) = file_stem {
if file_stem == "index" {
} else {
return file_stem.to_string_lossy().to_string();
match p.file_stem().map(|s| s.to_string_lossy()) {
Some(file_stem)
if !self
.config
.meaningless_file_names
.iter()
.any(|meaningless| file_stem.as_ref() == meaningless) =>
{
file_stem.into()
}
} else {
_ => self.get_block_name(
p.parent()
.expect("path only contains meaningless filenames (e.g. /index/index)?"),
),
}

self.get_block_name(p.parent().expect("/index/index/index?"))
}

fn get_display_name(&mut self, _: &Expr) -> JsWord {
Expand Down Expand Up @@ -338,20 +343,13 @@ impl VisitMut for DisplayNameAndId {
None
};

let display_name = if self.config.display_name {
Some(self.get_display_name(expr))
} else {
None
};

let display_name = self
.config
.display_name
.then(|| self.get_display_name(expr));
trace!("display_name: {:?}", display_name);

let component_id = if self.config.ssr {
Some(self.get_component_id().into())
} else {
None
};

let component_id = self.config.ssr.then(|| self.get_component_id().into());
trace!("component_id: {:?}", display_name);

self.add_config(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Port of https://github.com/styled-components/babel-plugin-styled-components/blob/a20c3033508677695953e7a434de4746168eeb4e/src/visitors/transpileCssProp.js

use std::cell::RefCell;
use std::rc::Rc;
use std::{borrow::Cow, collections::HashMap};

use crate::State;
use inflector::Inflector;
use once_cell::sync::Lazy;
use regex::Regex;
Expand All @@ -24,12 +27,17 @@ use super::top_level_binding_collector::collect_top_level_decls;
static TAG_NAME_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new("^[a-z][a-z\\d]*(\\-[a-z][a-z\\d]*)?$").unwrap());

pub fn transpile_css_prop() -> impl Fold + VisitMut {
as_folder(TranspileCssProp::default())
pub fn transpile_css_prop(state: Rc<RefCell<State>>) -> impl Fold + VisitMut {
as_folder(TranspileCssProp {
state,
..Default::default()
})
}

#[derive(Default)]
struct TranspileCssProp {
state: Rc<RefCell<State>>,

import_name: Option<Ident>,
injected_nodes: Vec<Stmt>,
interleaved_injections: AHashMap<Id, Vec<Stmt>>,
Expand Down Expand Up @@ -69,10 +77,18 @@ impl VisitMut for TranspileCssProp {
continue;
}

let import_name = self
.import_name
.get_or_insert_with(|| private_ident!("_styled"))
.clone();
let import_name = if let Some(ident) = self
.state
.borrow()
.import_local_name("default", None)
.map(Ident::from)
{
ident
} else {
self.import_name
.get_or_insert_with(|| private_ident!("_styled"))
.clone()
};

let name = get_name_ident(&elem.opening.name);
let id_sym = name.sym.to_class_case();
Expand Down Expand Up @@ -349,6 +365,7 @@ impl VisitMut for TranspileCssProp {
self.top_level_decls = None;

if let Some(import_name) = self.import_name.take() {
self.state.borrow_mut().set_import_name(import_name.to_id());
let specifier = ImportSpecifier::Default(ImportDefaultSpecifier {
span: DUMMY_SP,
local: import_name,
Expand Down
Loading