Skip to content

Commit

Permalink
refactor webpack loaders
Browse files Browse the repository at this point in the history
allow to apply existing pipeline
change webpack loader key to glob for more flexibility
add test cases
  • Loading branch information
sokra committed May 9, 2023
1 parent 201ab71 commit 8634d5c
Show file tree
Hide file tree
Showing 16 changed files with 262 additions and 90 deletions.
85 changes: 44 additions & 41 deletions packages/next-swc/crates/next-core/src/babel.rs
Expand Up @@ -6,9 +6,10 @@ use turbo_binding::{
issue::{Issue, IssueSeverity, IssueSeverityVc, IssueVc},
resolve::{parse::RequestVc, pattern::Pattern, resolve},
},
node::transforms::webpack::{WebpackLoaderConfigItem, WebpackLoaderConfigItemsVc},
node::transforms::webpack::{WebpackLoaderItem, WebpackLoaderItemsVc},
turbopack::{
module_options::WebpackLoadersOptionsVc, resolve_options,
module_options::{LoaderRuleItem, OptionWebpackRulesVc, WebpackRulesVc},
resolve_options,
resolve_options_context::ResolveOptionsContext,
},
},
Expand Down Expand Up @@ -36,8 +37,8 @@ const BABEL_CONFIG_FILES: &[&str] = &[
#[turbo_tasks::function]
pub async fn maybe_add_babel_loader(
project_root: FileSystemPathVc,
webpack_options: WebpackLoadersOptionsVc,
) -> Result<WebpackLoadersOptionsVc> {
webpack_rules: Option<WebpackRulesVc>,
) -> Result<OptionWebpackRulesVc> {
let has_babel_config = {
let mut has_babel_config = false;
for filename in BABEL_CONFIG_FILES {
Expand All @@ -51,30 +52,22 @@ pub async fn maybe_add_babel_loader(
};

if has_babel_config {
let mut options = (*webpack_options.await?).clone();
let mut rules = if let Some(webpack_rules) = webpack_rules {
webpack_rules.await?.clone_value()
} else {
Default::default()
};
let mut has_emitted_babel_resolve_issue = false;
for ext in [".js", ".jsx", ".ts", ".tsx", ".cjs", ".mjs"] {
let configs = options.extension_to_loaders.get(ext);
let has_babel_loader = match configs {
None => false,
Some(configs) => {
let mut has_babel_loader = false;
for config in &*configs.await? {
let name = match config {
WebpackLoaderConfigItem::LoaderName(name) => name,
WebpackLoaderConfigItem::LoaderNameWithOptions {
loader: name,
options: _,
} => name,
};

if name == "babel-loader" {
has_babel_loader = true;
break;
}
}
has_babel_loader
}
let mut has_changed = false;
for pattern in ["*.js", "*.jsx", "*.ts", "*.tsx", "*.cjs", "*.mjs"] {
let rule = rules.get_mut(pattern);
let has_babel_loader = if let Some(rule) = rule.as_ref() {
rule.loaders
.await?
.iter()
.any(|c| c.loader == "babel-loader")
} else {
false
};

if !has_babel_loader {
Expand All @@ -100,24 +93,34 @@ pub async fn maybe_add_babel_loader(
has_emitted_babel_resolve_issue = true;
}

let loader = WebpackLoaderConfigItem::LoaderName("babel-loader".to_owned());
options.extension_to_loaders.insert(
ext.to_owned(),
if options.extension_to_loaders.contains_key(ext) {
let mut new_configs = (*(options.extension_to_loaders[ext].await?)).clone();
new_configs.push(loader);
WebpackLoaderConfigItemsVc::cell(new_configs)
} else {
WebpackLoaderConfigItemsVc::cell(vec![loader])
},
);
let loader = WebpackLoaderItem {
loader: "babel-loader".to_string(),
options: Default::default(),
};
if let Some(rule) = rule {
let mut loaders = rule.loaders.await?.clone_value();
loaders.push(loader);
rule.loaders = WebpackLoaderItemsVc::cell(loaders);
} else {
rules.insert(
pattern.to_string(),
LoaderRuleItem {
loaders: WebpackLoaderItemsVc::cell(vec![loader]),
rename_as: Some("*".to_string()),
},
);
}
has_changed = true;
}
}

Ok(options.cell())
} else {
Ok(webpack_options)
if has_changed {
return Ok(OptionWebpackRulesVc::cell(Some(WebpackRulesVc::cell(
rules,
))));
}
}
Ok(OptionWebpackRulesVc::cell(webpack_rules))
}

#[turbo_tasks::function]
Expand Down
18 changes: 7 additions & 11 deletions packages/next-swc/crates/next-core/src/next_client/context.rs
Expand Up @@ -175,21 +175,17 @@ pub async fn get_client_module_options_context(
let decorators_options = get_decorators_transform_options(project_path);
let mdx_rs_options = *next_config.mdx_rs().await?;
let jsx_runtime_options = get_jsx_transform_options(project_path);
let enable_webpack_loaders = {
let options = &*next_config.webpack_loaders_options().await?;
let loaders_options = WebpackLoadersOptions {
extension_to_loaders: options.clone(),
let webpack_rules =
*maybe_add_babel_loader(project_path, *next_config.webpack_rules().await?).await?;
let enable_webpack_loaders = webpack_rules.map(|rules| {
WebpackLoadersOptions {
rules,
loader_runner_package: Some(get_external_next_compiled_package_mapping(
StringVc::cell("loader-runner".to_owned()),
)),
placeholder_for_future_extensions: (),
}
.cell();

maybe_add_babel_loader(project_path, loaders_options)
.await?
.clone_if()
};
.cell()
});

let enable_emotion = *get_emotion_compiler_config(next_config).await?;

Expand Down
140 changes: 121 additions & 19 deletions packages/next-swc/crates/next-core/src/next_config.rs
Expand Up @@ -11,7 +11,7 @@ use turbo_binding::{
chunk::ChunkingContext,
context::AssetContext,
ident::AssetIdentVc,
issue::IssueContextExt,
issue::{Issue, IssueContextExt, IssueSeverity, IssueSeverityVc, IssueVc},
reference_type::{EntryReferenceSubType, ReferenceType},
resolve::{
find_context_file,
Expand All @@ -27,16 +27,19 @@ use turbo_binding::{
node::{
evaluate::evaluate,
execution_context::{ExecutionContext, ExecutionContextVc},
transforms::webpack::{WebpackLoaderConfigItems, WebpackLoaderConfigItemsVc},
transforms::webpack::{WebpackLoaderItem, WebpackLoaderItemsVc},
},
turbopack::{
evaluate_context::node_evaluate_asset_context,
module_options::StyledComponentsTransformConfig,
module_options::{
LoaderRuleItem, OptionWebpackRulesVc, StyledComponentsTransformConfig,
WebpackRulesVc,
},
},
},
};
use turbo_tasks::{
primitives::{BoolVc, StringsVc},
primitives::{BoolVc, StringVc, StringsVc},
trace::TraceRawVcs,
CompletionVc, Value,
};
Expand Down Expand Up @@ -352,10 +355,30 @@ pub enum RemotePatternProtocal {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase")]
pub struct ExperimentalTurboConfig {
pub loaders: Option<IndexMap<String, WebpackLoaderConfigItems>>,
/// This option has been replace by `rules`.
pub loaders: Option<JsonValue>,
pub rules: Option<IndexMap<String, RuleConfigItem>>,
pub resolve_alias: Option<IndexMap<String, JsonValue>>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase", untagged)]
pub enum RuleConfigItem {
Loaders(Vec<LoaderItem>),
Options {
loaders: Vec<LoaderItem>,
#[serde(default, alias = "as")]
rename_as: Option<String>,
},
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(untagged)]
pub enum LoaderItem {
LoaderName(String),
LoaderOptions(WebpackLoaderItem),
}

#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase")]
pub struct ExperimentalConfig {
Expand Down Expand Up @@ -477,10 +500,6 @@ pub enum RemoveConsoleConfig {
Config { exclude: Option<Vec<String>> },
}

#[derive(Default)]
#[turbo_tasks::value(transparent)]
pub struct WebpackExtensionToLoaders(IndexMap<String, WebpackLoaderConfigItemsVc>);

#[turbo_tasks::value_impl]
impl NextConfigVc {
#[turbo_tasks::function]
Expand Down Expand Up @@ -555,19 +574,46 @@ impl NextConfigVc {
}

#[turbo_tasks::function]
pub async fn webpack_loaders_options(self) -> Result<WebpackExtensionToLoadersVc> {
pub async fn webpack_rules(self) -> Result<OptionWebpackRulesVc> {
let this = self.await?;
let Some(turbo_loaders) = this.experimental.turbo.as_ref().and_then(|t| t.loaders.as_ref()) else {
return Ok(WebpackExtensionToLoadersVc::cell(IndexMap::new()));
let Some(turbo_rules) = this.experimental.turbo.as_ref().and_then(|t| t.rules.as_ref()) else {
return Ok(OptionWebpackRulesVc::cell(None));
};
let mut extension_to_loaders = IndexMap::new();
for (ext, loaders) in turbo_loaders {
extension_to_loaders.insert(
ext.clone(),
WebpackLoaderConfigItemsVc::cell(loaders.0.clone()),
);
if turbo_rules.is_empty() {
return Ok(OptionWebpackRulesVc::cell(None));
}
let mut rules = IndexMap::new();
for (ext, rule) in turbo_rules {
fn transform_loaders(loaders: &[LoaderItem]) -> WebpackLoaderItemsVc {
WebpackLoaderItemsVc::cell(
loaders
.iter()
.map(|item| match item {
LoaderItem::LoaderName(name) => WebpackLoaderItem {
loader: name.clone(),
options: Default::default(),
},
LoaderItem::LoaderOptions(options) => options.clone(),
})
.collect(),
)
}
let rule = match rule {
RuleConfigItem::Loaders(loaders) => LoaderRuleItem {
loaders: transform_loaders(loaders),
rename_as: None,
},
RuleConfigItem::Options { loaders, rename_as } => LoaderRuleItem {
loaders: transform_loaders(loaders),
rename_as: rename_as.clone(),
},
};

rules.insert(ext.clone(), rule);
}
Ok(WebpackExtensionToLoaders(extension_to_loaders).cell())
Ok(OptionWebpackRulesVc::cell(Some(WebpackRulesVc::cell(
rules,
))))
}

#[turbo_tasks::function]
Expand Down Expand Up @@ -667,6 +713,23 @@ pub async fn load_next_config_internal(
};
let next_config: NextConfig = parse_json_with_source_context(val.to_str()?)?;

if let Some(turbo) = next_config.experimental.turbo.as_ref() {
if turbo.loaders.is_some() {
OutdatedConfigIssue {
path: config_file.unwrap_or(project_path),
old_name: "experimental.turbo.loaders".to_string(),
new_name: "experimental.turbo.rules".to_string(),
description: "The new option is similar, but the key should be a glob instead of \
an extension.
Example: loaders: { \".mdx\": [\"mdx-loader\"] } -> rules: { \"*.mdx\": [\"mdx-loader\"] }"
.to_string(),
}
.cell()
.as_issue()
.emit()
}
}

Ok(next_config.cell())
}

Expand All @@ -677,3 +740,42 @@ pub async fn has_next_config(context: FileSystemPathVc) -> Result<BoolVc> {
FindContextFileResult::NotFound(_)
)))
}

#[turbo_tasks::value]
struct OutdatedConfigIssue {
path: FileSystemPathVc,
old_name: String,
new_name: String,
description: String,
}

#[turbo_tasks::value_impl]
impl Issue for OutdatedConfigIssue {
#[turbo_tasks::function]
fn severity(&self) -> IssueSeverityVc {
IssueSeverity::Error.into()
}

#[turbo_tasks::function]
fn category(&self) -> StringVc {
StringVc::cell("config".to_string())
}

#[turbo_tasks::function]
fn context(&self) -> FileSystemPathVc {
self.path
}

#[turbo_tasks::function]
fn title(&self) -> StringVc {
StringVc::cell(format!(
"\"{}\" has been replaced by \"{}\"",
self.old_name, self.new_name
))
}

#[turbo_tasks::function]
fn description(&self) -> StringVc {
StringVc::cell(self.description.to_string())
}
}
18 changes: 7 additions & 11 deletions packages/next-swc/crates/next-core/src/next_server/context.rs
Expand Up @@ -258,21 +258,17 @@ pub async fn get_server_module_options_context(
..Default::default()
});

let enable_webpack_loaders = {
let options = &*next_config.webpack_loaders_options().await?;
let loaders_options = WebpackLoadersOptions {
extension_to_loaders: options.clone(),
let webpack_rules =
*maybe_add_babel_loader(project_path, *next_config.webpack_rules().await?).await?;
let enable_webpack_loaders = webpack_rules.map(|rules| {
WebpackLoadersOptions {
rules: rules.clone(),
loader_runner_package: Some(get_external_next_compiled_package_mapping(
StringVc::cell("loader-runner".to_owned()),
)),
placeholder_for_future_extensions: (),
}
.cell();

maybe_add_babel_loader(project_path, loaders_options)
.await?
.clone_if()
};
.cell()
});

let tsconfig = get_typescript_transform_options(project_path);
let decorators_options = get_decorators_transform_options(project_path);
Expand Down
@@ -0,0 +1,11 @@
module.exports = {
experimental: {
turbo: {
loaders: {
'.replace': [
{ loader: 'replace-loader', options: { defaultExport: 3 } },
],
},
},
},
}
@@ -0,0 +1,11 @@
import { useTestHarness } from '@turbo/pack-test-harness'

export default function Home() {
useTestHarness(runTests)

return null
}

function runTests() {
it('run', () => {})
}

0 comments on commit 8634d5c

Please sign in to comment.