optimize_barrel
SWC transform and new optimizePackageImports
config (#54572)
## Implementation Base on #54530, we're implementing a `optimize_barrel` transform to optimize barrel files to only export the names we need. If the transformed file isn't a "barrel file", we just re-export the names from it without any transformation. Take `lucide-react` as an example, with #54530 we are able to transform ```js import { IceCream } from 'lucide-react' ``` to ```js import { IceCream } from '__barrel_optimize__?names=IceCream!=!lucide-react?__barrel_optimize_noop__=IceCream' ``` And then, we apply that new request with a new Webpack module rule to use the SWC loader with option `optimizeBarrelExports: ['IceCream']`, which eventually got passed to this new `optimize_barrel` transform and does the optimization. ## Notes We'll have to add a new `getModularizeImportAliases` alias list to map `lucide-react` to the ESM version, as we have the `['main', 'module']` resolve order for the server compiler. Otherwise this optimization doesn't work in that compiler. There's no e2e test added because it's already covered by the `modularize-imports` test as we removed the default `lucide-react` transform rules and it still works. We'll need to test other libs before migrating them to the new `optimizePackageImports` option. --- Closes #54571, closes #53605, closes #53789, closes #53894, closes #54063.
This commit is contained in:
parent
eee137615e
commit
b4a5663c5f
26 changed files with 449 additions and 87 deletions
|
@ -59,6 +59,7 @@ pub mod disallow_re_export_all_in_page;
|
|||
pub mod named_import_transform;
|
||||
pub mod next_dynamic;
|
||||
pub mod next_ssg;
|
||||
pub mod optimize_barrel;
|
||||
pub mod page_config;
|
||||
pub mod react_remove_properties;
|
||||
pub mod react_server_components;
|
||||
|
@ -132,6 +133,9 @@ pub struct TransformOptions {
|
|||
#[serde(default)]
|
||||
pub auto_modularize_imports: Option<named_import_transform::Config>,
|
||||
|
||||
#[serde(default)]
|
||||
pub optimize_barrel_exports: Option<optimize_barrel::Config>,
|
||||
|
||||
#[serde(default)]
|
||||
pub font_loaders: Option<next_transform_font::Config>,
|
||||
|
||||
|
@ -253,6 +257,11 @@ where
|
|||
Some(config) => Either::Left(named_import_transform::named_import_transform(config.clone())),
|
||||
None => Either::Right(noop()),
|
||||
},
|
||||
match &opts.optimize_barrel_exports {
|
||||
Some(config) => Either::Left(optimize_barrel::optimize_barrel(
|
||||
file.name.clone(),config.clone())),
|
||||
None => Either::Right(noop()),
|
||||
},
|
||||
opts.emotion
|
||||
.as_ref()
|
||||
.and_then(|config| {
|
||||
|
|
|
@ -60,10 +60,10 @@ impl Fold for NamedImportTransform {
|
|||
}
|
||||
|
||||
if !skip_transform {
|
||||
let names = specifier_names.join(",");
|
||||
let new_src = format!(
|
||||
"barrel-optimize-loader?names={}!{}",
|
||||
specifier_names.join(","),
|
||||
src_value
|
||||
"__barrel_optimize__?names={}!=!{}?__barrel_optimize_noop__={}",
|
||||
names, src_value, names,
|
||||
);
|
||||
|
||||
// Create a new import declaration, keep everything the same except the source
|
||||
|
|
268
packages/next-swc/crates/core/src/optimize_barrel.rs
Normal file
268
packages/next-swc/crates/core/src/optimize_barrel.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
use serde::Deserialize;
|
||||
use turbopack_binding::swc::core::{
|
||||
common::{FileName, DUMMY_SP},
|
||||
ecma::{
|
||||
ast::*,
|
||||
utils::{private_ident, quote_str},
|
||||
visit::Fold,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub names: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn optimize_barrel(filename: FileName, config: Config) -> impl Fold {
|
||||
OptimizeBarrel {
|
||||
filepath: filename.to_string(),
|
||||
names: config.names,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OptimizeBarrel {
|
||||
filepath: String,
|
||||
names: Vec<String>,
|
||||
}
|
||||
|
||||
impl Fold for OptimizeBarrel {
|
||||
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
|
||||
// One pre-pass to find all the local idents that we are referencing.
|
||||
let mut local_idents = vec![];
|
||||
for item in &items {
|
||||
if let ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export_named)) = item {
|
||||
if export_named.src.is_none() {
|
||||
for spec in &export_named.specifiers {
|
||||
if let ExportSpecifier::Named(s) = spec {
|
||||
let str_name;
|
||||
if let Some(name) = &s.exported {
|
||||
str_name = match &name {
|
||||
ModuleExportName::Ident(n) => n.sym.to_string(),
|
||||
ModuleExportName::Str(n) => n.value.to_string(),
|
||||
};
|
||||
} else {
|
||||
str_name = match &s.orig {
|
||||
ModuleExportName::Ident(n) => n.sym.to_string(),
|
||||
ModuleExportName::Str(n) => n.value.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
// If the exported name needs to be kept, track the local ident.
|
||||
if self.names.contains(&str_name) {
|
||||
if let ModuleExportName::Ident(i) = &s.orig {
|
||||
local_idents.push(i.sym.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The second pass to rebuild the module items.
|
||||
let mut new_items = vec![];
|
||||
|
||||
// We only apply this optimization to barrel files. Here we consider
|
||||
// a barrel file to be a file that only exports from other modules.
|
||||
// Besides that, lit expressions are allowed as well ("use client", etc.).
|
||||
let mut is_barrel = true;
|
||||
for item in &items {
|
||||
match item {
|
||||
ModuleItem::ModuleDecl(decl) => {
|
||||
match decl {
|
||||
// export { foo } from './foo';
|
||||
ModuleDecl::ExportNamed(export_named) => {
|
||||
for spec in &export_named.specifiers {
|
||||
match spec {
|
||||
ExportSpecifier::Namespace(s) => {
|
||||
let name_str = match &s.name {
|
||||
ModuleExportName::Ident(n) => n.sym.to_string(),
|
||||
ModuleExportName::Str(n) => n.value.to_string(),
|
||||
};
|
||||
if self.names.contains(&name_str) {
|
||||
new_items.push(item.clone());
|
||||
}
|
||||
}
|
||||
ExportSpecifier::Named(s) => {
|
||||
if let Some(name) = &s.exported {
|
||||
let name_str = match &name {
|
||||
ModuleExportName::Ident(n) => n.sym.to_string(),
|
||||
ModuleExportName::Str(n) => n.value.to_string(),
|
||||
};
|
||||
|
||||
if self.names.contains(&name_str) {
|
||||
new_items.push(ModuleItem::ModuleDecl(
|
||||
ModuleDecl::ExportNamed(NamedExport {
|
||||
span: DUMMY_SP,
|
||||
specifiers: vec![ExportSpecifier::Named(
|
||||
ExportNamedSpecifier {
|
||||
span: DUMMY_SP,
|
||||
orig: s.orig.clone(),
|
||||
exported: Some(
|
||||
ModuleExportName::Ident(
|
||||
Ident::new(
|
||||
name_str.into(),
|
||||
DUMMY_SP,
|
||||
),
|
||||
),
|
||||
),
|
||||
is_type_only: false,
|
||||
},
|
||||
)],
|
||||
src: export_named.src.clone(),
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
}),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let name_str = match &s.orig {
|
||||
ModuleExportName::Ident(n) => n.sym.to_string(),
|
||||
ModuleExportName::Str(n) => n.value.to_string(),
|
||||
};
|
||||
|
||||
if self.names.contains(&name_str) {
|
||||
new_items.push(ModuleItem::ModuleDecl(
|
||||
ModuleDecl::ExportNamed(NamedExport {
|
||||
span: DUMMY_SP,
|
||||
specifiers: vec![ExportSpecifier::Named(
|
||||
ExportNamedSpecifier {
|
||||
span: DUMMY_SP,
|
||||
orig: s.orig.clone(),
|
||||
exported: None,
|
||||
is_type_only: false,
|
||||
},
|
||||
)],
|
||||
src: export_named.src.clone(),
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
is_barrel = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keep import statements that create the local idents we need.
|
||||
ModuleDecl::Import(import_decl) => {
|
||||
for spec in &import_decl.specifiers {
|
||||
match spec {
|
||||
ImportSpecifier::Named(s) => {
|
||||
if local_idents.contains(&s.local.sym) {
|
||||
new_items.push(ModuleItem::ModuleDecl(
|
||||
ModuleDecl::Import(ImportDecl {
|
||||
span: DUMMY_SP,
|
||||
specifiers: vec![ImportSpecifier::Named(
|
||||
ImportNamedSpecifier {
|
||||
span: DUMMY_SP,
|
||||
local: s.local.clone(),
|
||||
imported: s.imported.clone(),
|
||||
is_type_only: false,
|
||||
},
|
||||
)],
|
||||
src: import_decl.src.clone(),
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
ImportSpecifier::Default(s) => {
|
||||
if local_idents.contains(&s.local.sym) {
|
||||
new_items.push(ModuleItem::ModuleDecl(
|
||||
ModuleDecl::Import(ImportDecl {
|
||||
span: DUMMY_SP,
|
||||
specifiers: vec![ImportSpecifier::Default(
|
||||
ImportDefaultSpecifier {
|
||||
span: DUMMY_SP,
|
||||
local: s.local.clone(),
|
||||
},
|
||||
)],
|
||||
src: import_decl.src.clone(),
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
ImportSpecifier::Namespace(s) => {
|
||||
if local_idents.contains(&s.local.sym) {
|
||||
new_items.push(ModuleItem::ModuleDecl(
|
||||
ModuleDecl::Import(ImportDecl {
|
||||
span: DUMMY_SP,
|
||||
specifiers: vec![ImportSpecifier::Namespace(
|
||||
ImportStarAsSpecifier {
|
||||
span: DUMMY_SP,
|
||||
local: s.local.clone(),
|
||||
},
|
||||
)],
|
||||
src: import_decl.src.clone(),
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Export expressions are not allowed in barrel files.
|
||||
is_barrel = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ModuleItem::Stmt(stmt) => match stmt {
|
||||
Stmt::Expr(expr) => match &*expr.expr {
|
||||
Expr::Lit(_) => {
|
||||
new_items.push(item.clone());
|
||||
}
|
||||
_ => {
|
||||
is_barrel = false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
is_barrel = false;
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If the file is not a barrel file, we need to create a new module that
|
||||
// re-exports from the original file.
|
||||
// This is to avoid creating multiple instances of the original module.
|
||||
if !is_barrel {
|
||||
new_items = vec![ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
|
||||
NamedExport {
|
||||
span: DUMMY_SP,
|
||||
specifiers: self
|
||||
.names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
ExportSpecifier::Named(ExportNamedSpecifier {
|
||||
span: DUMMY_SP,
|
||||
orig: ModuleExportName::Ident(private_ident!(name.clone())),
|
||||
exported: None,
|
||||
is_type_only: false,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
src: Some(Box::new(quote_str!(self.filepath.to_string()))),
|
||||
type_only: false,
|
||||
asserts: None,
|
||||
},
|
||||
))];
|
||||
}
|
||||
|
||||
new_items
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ use next_swc::{
|
|||
named_import_transform::named_import_transform,
|
||||
next_dynamic::next_dynamic,
|
||||
next_ssg::next_ssg,
|
||||
optimize_barrel::optimize_barrel,
|
||||
page_config::page_config_test,
|
||||
react_remove_properties::remove_properties,
|
||||
react_server_components::server_components,
|
||||
|
@ -481,6 +482,35 @@ fn named_import_transform_fixture(input: PathBuf) {
|
|||
);
|
||||
}
|
||||
|
||||
#[fixture("tests/fixture/optimize-barrel/**/input.js")]
|
||||
fn optimize_barrel_fixture(input: PathBuf) {
|
||||
let output = input.parent().unwrap().join("output.js");
|
||||
test_fixture(
|
||||
syntax(),
|
||||
&|_tr| {
|
||||
let unresolved_mark = Mark::new();
|
||||
let top_level_mark = Mark::new();
|
||||
|
||||
chain!(
|
||||
resolver(unresolved_mark, top_level_mark, false),
|
||||
optimize_barrel(
|
||||
FileName::Real(PathBuf::from("/some-project/node_modules/foo/file.js")),
|
||||
json(
|
||||
r#"
|
||||
{
|
||||
"names": ["x", "y", "z"]
|
||||
}
|
||||
"#
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
&input,
|
||||
&output,
|
||||
Default::default(),
|
||||
);
|
||||
}
|
||||
|
||||
fn json<T>(s: &str) -> T
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { A, B, C as F } from "barrel-optimize-loader?names=A,B,C!foo";
|
||||
import { A, B, C as F } from "__barrel_optimize__?names=A,B,C!=!foo?__barrel_optimize_noop__=A,B,C";
|
||||
import D from 'bar';
|
||||
import E from 'baz';
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { A, B, C as F } from "barrel-optimize-loader?names=A,B,C!foo";
|
||||
import { D } from "barrel-optimize-loader?names=D!bar";
|
||||
import { A, B, C as F } from "__barrel_optimize__?names=A,B,C!=!foo?__barrel_optimize_noop__=A,B,C";
|
||||
import { D } from "__barrel_optimize__?names=D!=!bar?__barrel_optimize_noop__=D";
|
||||
import E from 'baz';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export { foo, b as y } from './1'
|
||||
export { x, a } from './2'
|
||||
export { z }
|
|
@ -0,0 +1,3 @@
|
|||
export { b as y } from './1';
|
||||
export { x } from './2';
|
||||
export { z };
|
|
@ -0,0 +1,6 @@
|
|||
// De-optimize this file
|
||||
const foo = 1
|
||||
|
||||
export { foo, b as y } from './1'
|
||||
export { x, a } from './2'
|
||||
export { z }
|
|
@ -0,0 +1,2 @@
|
|||
// De-optimize this file
|
||||
export { x, y, z } from "/some-project/node_modules/foo/file.js";
|
|
@ -0,0 +1,6 @@
|
|||
// De-optimize this file
|
||||
export * from 'x'
|
||||
|
||||
export { foo, b as y } from './1'
|
||||
export { x, a } from './2'
|
||||
export { z }
|
|
@ -0,0 +1,2 @@
|
|||
// De-optimize this file
|
||||
export { x, y, z } from "/some-project/node_modules/foo/file.js";
|
|
@ -0,0 +1,9 @@
|
|||
'use client'
|
||||
|
||||
import foo, { a, b } from 'foo'
|
||||
import z from 'bar'
|
||||
|
||||
export { a as x }
|
||||
export { y } from '1'
|
||||
export { b }
|
||||
export { foo as default, z }
|
|
@ -0,0 +1,7 @@
|
|||
'use client';
|
||||
import { a } from 'foo'
|
||||
import z from 'bar'
|
||||
|
||||
export { a as x };
|
||||
export { y } from '1';
|
||||
export { z };
|
|
@ -0,0 +1,2 @@
|
|||
import * as index from './icons/index.js'
|
||||
export { index as x }
|
|
@ -0,0 +1,2 @@
|
|||
import * as index from './icons/index.js'
|
||||
export { index as x }
|
|
@ -79,6 +79,7 @@ fn test(input: &Path, minify: bool) {
|
|||
server_actions: None,
|
||||
cjs_require_optimizer: None,
|
||||
auto_modularize_imports: None,
|
||||
optimize_barrel_exports: None,
|
||||
};
|
||||
|
||||
let unresolved_mark = Mark::new();
|
||||
|
|
|
@ -301,6 +301,7 @@ export function getLoaderSWCOptions({
|
|||
isPageFile,
|
||||
hasReactRefresh,
|
||||
modularizeImports,
|
||||
optimizePackageImports,
|
||||
swcPlugins,
|
||||
compilerOptions,
|
||||
jsConfig,
|
||||
|
@ -310,6 +311,7 @@ export function getLoaderSWCOptions({
|
|||
hasServerComponents,
|
||||
isServerLayer,
|
||||
isServerActionsEnabled,
|
||||
optimizeBarrelExports,
|
||||
}: // This is not passed yet as "paths" resolving is handled by webpack currently.
|
||||
// resolvedBaseUrl,
|
||||
{
|
||||
|
@ -321,6 +323,9 @@ export function getLoaderSWCOptions({
|
|||
isPageFile: boolean
|
||||
hasReactRefresh: boolean
|
||||
modularizeImports: NextConfig['modularizeImports']
|
||||
optimizePackageImports?: NonNullable<
|
||||
NextConfig['experimental']
|
||||
>['optimizePackageImports']
|
||||
swcPlugins: ExperimentalConfig['swcPlugins']
|
||||
compilerOptions: NextConfig['compiler']
|
||||
jsConfig: any
|
||||
|
@ -330,6 +335,7 @@ export function getLoaderSWCOptions({
|
|||
hasServerComponents?: boolean
|
||||
isServerLayer: boolean
|
||||
isServerActionsEnabled?: boolean
|
||||
optimizeBarrelExports?: string[]
|
||||
}) {
|
||||
let baseOptions: any = getBaseSWCOptions({
|
||||
filename,
|
||||
|
@ -370,10 +376,17 @@ export function getLoaderSWCOptions({
|
|||
},
|
||||
},
|
||||
}
|
||||
baseOptions.autoModularizeImports = {
|
||||
packages: [
|
||||
// TODO: Add a list of packages that should be optimized by default
|
||||
],
|
||||
|
||||
// Modularize import optimization for barrel files
|
||||
if (optimizePackageImports) {
|
||||
baseOptions.autoModularizeImports = {
|
||||
packages: optimizePackageImports,
|
||||
}
|
||||
}
|
||||
if (optimizeBarrelExports) {
|
||||
baseOptions.optimizeBarrelExports = {
|
||||
names: optimizeBarrelExports,
|
||||
}
|
||||
}
|
||||
|
||||
const isNextDist = nextDistPath.test(filename)
|
||||
|
|
|
@ -532,6 +532,27 @@ function getOptimizedAliases(): { [pkg: string]: string } {
|
|||
)
|
||||
}
|
||||
|
||||
// Alias these modules to be resolved with "module" if possible.
|
||||
function getModularizeImportAliases(packages: string[]) {
|
||||
const aliases: { [pkg: string]: string } = {}
|
||||
const mainFields = ['module', 'main']
|
||||
|
||||
for (const pkg of packages) {
|
||||
try {
|
||||
const descriptionFileData = require(`${pkg}/package.json`)
|
||||
|
||||
for (const field of mainFields) {
|
||||
if (descriptionFileData.hasOwnProperty(field)) {
|
||||
aliases[pkg] = `${pkg}/${descriptionFileData[field]}`
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return aliases
|
||||
}
|
||||
|
||||
export function attachReactRefresh(
|
||||
webpackConfig: webpack.Configuration,
|
||||
targetLoader: webpack.RuleSetUseItem
|
||||
|
@ -1165,6 +1186,14 @@ export default async function getBaseWebpackConfig(
|
|||
...(isClient || isEdgeServer ? getOptimizedAliases() : {}),
|
||||
...(reactProductionProfiling ? getReactProfilingInProduction() : {}),
|
||||
|
||||
// For Node server, we need to re-alias the package imports to prefer to
|
||||
// resolve to the module export.
|
||||
...(isNodeServer
|
||||
? getModularizeImportAliases(
|
||||
config.experimental.optimizePackageImports || []
|
||||
)
|
||||
: {}),
|
||||
|
||||
[RSC_ACTION_VALIDATE_ALIAS]:
|
||||
'next/dist/build/webpack/loaders/next-flight-loader/action-validate',
|
||||
|
||||
|
@ -1397,6 +1426,12 @@ export default async function getBaseWebpackConfig(
|
|||
return
|
||||
}
|
||||
|
||||
// __barrel_optimize__ is a special marker that tells Next.js to
|
||||
// optimize the import by removing unused exports. This has to be compiled.
|
||||
if (request.startsWith('__barrel_optimize__')) {
|
||||
return
|
||||
}
|
||||
|
||||
// When in esm externals mode, and using import, we resolve with
|
||||
// ESM resolving options.
|
||||
// Also disable esm request when appDir is enabled
|
||||
|
@ -1936,7 +1971,6 @@ export default async function getBaseWebpackConfig(
|
|||
'next-invalid-import-error-loader',
|
||||
'next-metadata-route-loader',
|
||||
'modularize-import-loader',
|
||||
'barrel-optimize-loader',
|
||||
].reduce((alias, loader) => {
|
||||
// using multiple aliases to replace `resolveLoader.modules`
|
||||
alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
|
||||
|
@ -1951,6 +1985,25 @@ export default async function getBaseWebpackConfig(
|
|||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /__barrel_optimize__/,
|
||||
use: ({
|
||||
resourceQuery,
|
||||
issuerLayer,
|
||||
}: {
|
||||
resourceQuery: string
|
||||
issuerLayer: string
|
||||
}) => {
|
||||
const names = resourceQuery.slice('?names='.length).split(',')
|
||||
return [
|
||||
getSwcLoader({
|
||||
isServerLayer:
|
||||
issuerLayer === WEBPACK_LAYERS.reactServerComponents,
|
||||
optimizeBarrelExports: names,
|
||||
}),
|
||||
]
|
||||
},
|
||||
},
|
||||
...(hasAppDir
|
||||
? [
|
||||
{
|
||||
|
@ -2006,9 +2059,7 @@ export default async function getBaseWebpackConfig(
|
|||
...(hasAppDir && !isClient
|
||||
? [
|
||||
{
|
||||
issuerLayer: {
|
||||
or: [isWebpackServerLayer],
|
||||
},
|
||||
issuerLayer: isWebpackServerLayer,
|
||||
test: {
|
||||
// Resolve it if it is a source code file, and it has NOT been
|
||||
// opted out of bundling.
|
||||
|
@ -2144,9 +2195,7 @@ export default async function getBaseWebpackConfig(
|
|||
? [
|
||||
{
|
||||
test: codeCondition.test,
|
||||
issuerLayer: {
|
||||
or: [isWebpackServerLayer],
|
||||
},
|
||||
issuerLayer: isWebpackServerLayer,
|
||||
exclude: [asyncStoragesRegex],
|
||||
use: swcLoaderForServerLayer,
|
||||
},
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export default function transformSource(this: any, source: string) {
|
||||
// const { names }: any = this.getOptions()
|
||||
// const { resourcePath } = this
|
||||
return source
|
||||
}
|
|
@ -53,6 +53,7 @@ async function loaderTransform(
|
|||
swcCacheDir,
|
||||
hasServerComponents,
|
||||
isServerLayer,
|
||||
optimizeBarrelExports,
|
||||
} = loaderOptions
|
||||
const isPageFile = filename.startsWith(pagesDir)
|
||||
const relativeFilePathFromRoot = path.relative(rootDir, filename)
|
||||
|
@ -66,6 +67,7 @@ async function loaderTransform(
|
|||
development: this.mode === 'development',
|
||||
hasReactRefresh,
|
||||
modularizeImports: nextConfig?.modularizeImports,
|
||||
optimizePackageImports: nextConfig?.experimental?.optimizePackageImports,
|
||||
swcPlugins: nextConfig?.experimental?.swcPlugins,
|
||||
compilerOptions: nextConfig?.compiler,
|
||||
jsConfig,
|
||||
|
@ -75,6 +77,7 @@ async function loaderTransform(
|
|||
hasServerComponents,
|
||||
isServerActionsEnabled: nextConfig?.experimental?.serverActions,
|
||||
isServerLayer,
|
||||
optimizeBarrelExports,
|
||||
})
|
||||
|
||||
const programmaticOptions = {
|
||||
|
|
|
@ -473,6 +473,9 @@ const configSchema = {
|
|||
},
|
||||
},
|
||||
},
|
||||
optimizePackageImports: {
|
||||
type: 'array',
|
||||
},
|
||||
instrumentationHook: {
|
||||
type: 'boolean',
|
||||
},
|
||||
|
|
|
@ -236,6 +236,11 @@ export interface ExperimentalConfig {
|
|||
|
||||
webVitalsAttribution?: Array<(typeof WEB_VITALS)[number]>
|
||||
|
||||
/**
|
||||
* Automatically apply the "modularizeImports" optimization to imports of the specified packages.
|
||||
*/
|
||||
optimizePackageImports?: string[]
|
||||
|
||||
turbo?: ExperimentalTurboOptions
|
||||
turbotrace?: {
|
||||
logLevel?:
|
||||
|
|
|
@ -678,48 +678,6 @@ function assignDefaults(
|
|||
'lodash-es': {
|
||||
transform: 'lodash-es/{{member}}',
|
||||
},
|
||||
'lucide-react': {
|
||||
// Note that we need to first resolve to the base path (`lucide-react`) and join the subpath,
|
||||
// instead of just resolving `lucide-react/esm/icons/{{kebabCase member}}` because this package
|
||||
// doesn't have proper `exports` fields for individual icons in its package.json.
|
||||
transform: {
|
||||
// Special aliases
|
||||
'(SortAsc|LucideSortAsc|SortAscIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/arrow-up-narrow-wide!lucide-react',
|
||||
'(SortDesc|LucideSortDesc|SortDescIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/arrow-down-wide-narrow!lucide-react',
|
||||
'(Verified|LucideVerified|VerifiedIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/badge-check!lucide-react',
|
||||
'(Slash|LucideSlash|SlashIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/ban!lucide-react',
|
||||
'(CurlyBraces|LucideCurlyBraces|CurlyBracesIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/braces!lucide-react',
|
||||
'(CircleSlashed|LucideCircleSlashed|CircleSlashedIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/circle-slash-2!lucide-react',
|
||||
'(SquareGantt|LucideSquareGantt|SquareGanttIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/gantt-chart-square!lucide-react',
|
||||
'(SquareKanbanDashed|LucideSquareKanbanDashed|SquareKanbanDashedIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/kanban-square-dashed!lucide-react',
|
||||
'(SquareKanban|LucideSquareKanban|SquareKanbanIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/kanban-square!lucide-react',
|
||||
'(Edit3|LucideEdit3|Edit3Icon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/pen-line!lucide-react',
|
||||
'(Edit|LucideEdit|EditIcon|PenBox|LucidePenBox|PenBoxIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/pen-square!lucide-react',
|
||||
'(Edit2|LucideEdit2|Edit2Icon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/pen!lucide-react',
|
||||
'(Stars|LucideStars|StarsIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/sparkles!lucide-react',
|
||||
'(TextSelection|LucideTextSelection|TextSelectionIcon)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/text-select!lucide-react',
|
||||
// General rules
|
||||
'Lucide(.*)':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/{{ kebabCase memberMatches.[1] }}!lucide-react',
|
||||
'(.*)Icon':
|
||||
'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/{{ kebabCase memberMatches.[1] }}!lucide-react',
|
||||
'*': 'modularize-import-loader?name={{ member }}&from=default&as=default&join=../esm/icons/{{ kebabCase member }}!lucide-react',
|
||||
},
|
||||
},
|
||||
'@headlessui/react': {
|
||||
transform: {
|
||||
Transition:
|
||||
|
@ -775,6 +733,15 @@ function assignDefaults(
|
|||
},
|
||||
}
|
||||
|
||||
const userProvidedOptimizePackageImports =
|
||||
result.experimental?.optimizePackageImports || []
|
||||
if (!result.experimental) {
|
||||
result.experimental = {}
|
||||
}
|
||||
result.experimental.optimizePackageImports = [
|
||||
...new Set([...userProvidedOptimizePackageImports, 'lucide-react']),
|
||||
]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
export const metadata = {
|
||||
title: 'Next.js',
|
||||
description: 'Generated by Next.js',
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
'use client'
|
||||
|
||||
import { IceCream } from 'lucide-react'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<IceCream />
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue