fixes to allow lazy compilation for import() (#32441)

This commit is contained in:
Tobias Koppers 2021-12-14 11:33:04 +01:00 committed by GitHub
parent 67545bd654
commit ee220730db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 690 additions and 375 deletions

View file

@ -12,6 +12,7 @@ examples/with-jest/**
examples/with-mobx-state-tree/**
examples/with-mobx/**
packages/next/bundles/webpack/packages/*.runtime.js
packages/next/bundles/webpack/packages/lazy-compilation-*.js
packages/next/compiled/**/*
packages/react-refresh-utils/**/*.js
packages/react-dev-overlay/lib/**

View file

@ -3,6 +3,7 @@ node_modules
**/_next/**
**/dist/**
packages/next/bundles/webpack/packages/*.runtime.js
packages/next/bundles/webpack/packages/lazy-compilation-*.js
packages/next/compiled/**
packages/react-refresh-utils/**/*.js
packages/react-refresh-utils/**/*.d.ts

View file

@ -79,6 +79,9 @@ pub struct TransformOptions {
#[serde(default)]
pub is_development: bool,
#[serde(default)]
pub is_server: bool,
#[serde(default)]
pub styled_components: Option<styled_components::Config>,
@ -113,7 +116,12 @@ pub fn custom_before_pass(file: Arc<SourceFile>, opts: &TransformOptions) -> imp
},
Optional::new(next_ssg::next_ssg(), !opts.disable_next_ssg),
amp_attributes::amp_attributes(),
next_dynamic::next_dynamic(file.name.clone(), opts.pages_dir.clone()),
next_dynamic::next_dynamic(
opts.is_development,
opts.is_server,
file.name.clone(),
opts.pages_dir.clone()
),
Optional::new(
page_config::page_config(opts.is_development, opts.is_page_file),
!opts.disable_page_config

View file

@ -4,93 +4,105 @@ use pathdiff::diff_paths;
use swc_atoms::js_word;
use swc_common::{FileName, DUMMY_SP};
use swc_ecmascript::ast::{
ArrayLit, ArrowExpr, BinExpr, BinaryOp, BlockStmtOrExpr, CallExpr, Expr, ExprOrSpread,
ExprOrSuper, Ident, ImportDecl, ImportSpecifier, KeyValueProp, Lit, MemberExpr, ObjectLit, Prop,
PropName, PropOrSpread, Str, StrKind,
ArrayLit, ArrowExpr, BinExpr, BinaryOp, BlockStmtOrExpr, CallExpr, Expr, ExprOrSpread,
ExprOrSuper, Ident, ImportDecl, ImportSpecifier, KeyValueProp, Lit, MemberExpr, ObjectLit,
Prop, PropName, PropOrSpread, Str, StrKind,
};
use swc_ecmascript::utils::{
ident::{Id, IdentLike},
HANDLER,
ident::{Id, IdentLike},
HANDLER,
};
use swc_ecmascript::visit::{Fold, FoldWith};
pub fn next_dynamic(filename: FileName, pages_dir: Option<PathBuf>) -> impl Fold {
NextDynamicPatcher {
pages_dir,
filename,
dynamic_bindings: vec![],
is_next_dynamic_first_arg: false,
dynamically_imported_specifier: None,
}
pub fn next_dynamic(
is_development: bool,
is_server: bool,
filename: FileName,
pages_dir: Option<PathBuf>,
) -> impl Fold {
NextDynamicPatcher {
is_development,
is_server,
pages_dir,
filename,
dynamic_bindings: vec![],
is_next_dynamic_first_arg: false,
dynamically_imported_specifier: None,
}
}
#[derive(Debug)]
struct NextDynamicPatcher {
pages_dir: Option<PathBuf>,
filename: FileName,
dynamic_bindings: Vec<Id>,
is_next_dynamic_first_arg: bool,
dynamically_imported_specifier: Option<String>,
is_development: bool,
is_server: bool,
pages_dir: Option<PathBuf>,
filename: FileName,
dynamic_bindings: Vec<Id>,
is_next_dynamic_first_arg: bool,
dynamically_imported_specifier: Option<String>,
}
impl Fold for NextDynamicPatcher {
fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl {
let ImportDecl {
ref src,
ref specifiers,
..
} = decl;
if &src.value == "next/dynamic" {
for specifier in specifiers {
if let ImportSpecifier::Default(default_specifier) = specifier {
self.dynamic_bindings.push(default_specifier.local.to_id());
}
}
}
decl
}
fn fold_call_expr(&mut self, expr: CallExpr) -> CallExpr {
if self.is_next_dynamic_first_arg {
if let ExprOrSuper::Expr(e) = &expr.callee {
if let Expr::Ident(Ident { sym, .. }) = &**e {
if sym == "import" {
if let Expr::Lit(Lit::Str(Str { value, .. })) = &*expr.args[0].expr {
self.dynamically_imported_specifier = Some(value.to_string());
fn fold_import_decl(&mut self, decl: ImportDecl) -> ImportDecl {
let ImportDecl {
ref src,
ref specifiers,
..
} = decl;
if &src.value == "next/dynamic" {
for specifier in specifiers {
if let ImportSpecifier::Default(default_specifier) = specifier {
self.dynamic_bindings.push(default_specifier.local.to_id());
}
}
}
}
}
return expr.fold_children_with(self);
decl
}
let mut expr = expr.fold_children_with(self);
if let ExprOrSuper::Expr(i) = &expr.callee {
if let Expr::Ident(identifier) = &**i {
if self.dynamic_bindings.contains(&identifier.to_id()) {
if expr.args.len() == 0 {
HANDLER.with(|handler| {
handler
.struct_span_err(
identifier.span,
"next/dynamic requires at least one argument",
)
.emit()
});
return expr;
} else if expr.args.len() > 2 {
HANDLER.with(|handler| {
handler
.struct_span_err(identifier.span, "next/dynamic only accepts 2 arguments")
.emit()
});
return expr;
}
if expr.args.len() == 2 {
match &*expr.args[1].expr {
Expr::Object(_) => {},
_ => {
HANDLER.with(|handler| {
fn fold_call_expr(&mut self, expr: CallExpr) -> CallExpr {
if self.is_next_dynamic_first_arg {
if let ExprOrSuper::Expr(e) = &expr.callee {
if let Expr::Ident(Ident { sym, .. }) = &**e {
if sym == "import" {
if let Expr::Lit(Lit::Str(Str { value, .. })) = &*expr.args[0].expr {
self.dynamically_imported_specifier = Some(value.to_string());
}
}
}
}
return expr.fold_children_with(self);
}
let mut expr = expr.fold_children_with(self);
if let ExprOrSuper::Expr(i) = &expr.callee {
if let Expr::Ident(identifier) = &**i {
if self.dynamic_bindings.contains(&identifier.to_id()) {
if expr.args.len() == 0 {
HANDLER.with(|handler| {
handler
.struct_span_err(
identifier.span,
"next/dynamic requires at least one argument",
)
.emit()
});
return expr;
} else if expr.args.len() > 2 {
HANDLER.with(|handler| {
handler
.struct_span_err(
identifier.span,
"next/dynamic only accepts 2 arguments",
)
.emit()
});
return expr;
}
if expr.args.len() == 2 {
match &*expr.args[1].expr {
Expr::Object(_) => {}
_ => {
HANDLER.with(|handler| {
handler
.struct_span_err(
identifier.span,
@ -98,170 +110,182 @@ impl Fold for NextDynamicPatcher {
)
.emit();
});
return expr;
}
}
}
return expr;
}
}
}
self.is_next_dynamic_first_arg = true;
expr.args[0].expr = expr.args[0].expr.clone().fold_with(self);
self.is_next_dynamic_first_arg = false;
self.is_next_dynamic_first_arg = true;
expr.args[0].expr = expr.args[0].expr.clone().fold_with(self);
self.is_next_dynamic_first_arg = false;
if let None = self.dynamically_imported_specifier {
return expr;
}
if let None = self.dynamically_imported_specifier {
return expr;
}
// dev client or server:
// loadableGenerated: {
// modules:
// ["/project/src/file-being-transformed.js -> " + '../components/hello'] }
// loadableGenerated: {
// webpack: () => [require.resolveWeak('../components/hello')],
// modules:
// ["/project/src/file-being-transformed.js -> " + '../components/hello'] }
let generated = Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
props: vec![
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new("webpack".into(), DUMMY_SP)),
value: Box::new(Expr::Arrow(ArrowExpr {
params: vec![],
body: BlockStmtOrExpr::Expr(Box::new(Expr::Array(ArrayLit {
elems: vec![Some(ExprOrSpread {
expr: Box::new(Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(Box::new(Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(Box::new(Expr::Ident(Ident {
sym: js_word!("require"),
span: DUMMY_SP,
optional: false,
}))),
prop: Box::new(Expr::Ident(Ident {
sym: "resolveWeak".into(),
span: DUMMY_SP,
optional: false,
})),
computed: false,
span: DUMMY_SP,
}))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: self
.dynamically_imported_specifier
.as_ref()
.unwrap()
.clone()
.into(),
span: DUMMY_SP,
kind: StrKind::Synthesized {},
has_escape: false,
}))),
spread: None,
}],
// prod client
// loadableGenerated: {
// webpack: () => [require.resolveWeak('../components/hello')],
let generated = Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
type_args: None,
})),
spread: None,
})],
span: DUMMY_SP,
}))),
is_async: false,
is_generator: false,
span: DUMMY_SP,
return_type: None,
type_params: None,
})),
}))),
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new("modules".into(), DUMMY_SP)),
value: Box::new(Expr::Array(ArrayLit {
elems: vec![Some(ExprOrSpread {
expr: Box::new(Expr::Bin(BinExpr {
span: DUMMY_SP,
op: BinaryOp::Add,
left: Box::new(Expr::Lit(Lit::Str(Str {
value: format!(
"{} -> ",
rel_filename(self.pages_dir.as_deref(), &self.filename)
)
.into(),
span: DUMMY_SP,
kind: StrKind::Synthesized {},
has_escape: false,
}))),
right: Box::new(Expr::Lit(Lit::Str(Str {
value: self
.dynamically_imported_specifier
.as_ref()
.unwrap()
.clone()
.into(),
span: DUMMY_SP,
kind: StrKind::Normal {
contains_quote: false,
props: if self.is_development || self.is_server {
vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new("modules".into(), DUMMY_SP)),
value: Box::new(Expr::Array(ArrayLit {
elems: vec![Some(ExprOrSpread {
expr: Box::new(Expr::Bin(BinExpr {
span: DUMMY_SP,
op: BinaryOp::Add,
left: Box::new(Expr::Lit(Lit::Str(Str {
value: format!(
"{} -> ",
rel_filename(
self.pages_dir.as_deref(),
&self.filename
)
)
.into(),
span: DUMMY_SP,
kind: StrKind::Synthesized {},
has_escape: false,
}))),
right: Box::new(Expr::Lit(Lit::Str(Str {
value: self
.dynamically_imported_specifier
.as_ref()
.unwrap()
.clone()
.into(),
span: DUMMY_SP,
kind: StrKind::Normal {
contains_quote: false,
},
has_escape: false,
}))),
})),
spread: None,
})],
span: DUMMY_SP,
})),
})))]
} else {
vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new("webpack".into(), DUMMY_SP)),
value: Box::new(Expr::Arrow(ArrowExpr {
params: vec![],
body: BlockStmtOrExpr::Expr(Box::new(Expr::Array(ArrayLit {
elems: vec![Some(ExprOrSpread {
expr: Box::new(Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(Box::new(Expr::Member(
MemberExpr {
obj: ExprOrSuper::Expr(Box::new(
Expr::Ident(Ident {
sym: js_word!("require"),
span: DUMMY_SP,
optional: false,
}),
)),
prop: Box::new(Expr::Ident(Ident {
sym: "resolveWeak".into(),
span: DUMMY_SP,
optional: false,
})),
computed: false,
span: DUMMY_SP,
},
))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: self
.dynamically_imported_specifier
.as_ref()
.unwrap()
.clone()
.into(),
span: DUMMY_SP,
kind: StrKind::Synthesized {},
has_escape: false,
}))),
spread: None,
}],
span: DUMMY_SP,
type_args: None,
})),
spread: None,
})],
span: DUMMY_SP,
}))),
is_async: false,
is_generator: false,
span: DUMMY_SP,
return_type: None,
type_params: None,
})),
})))]
},
has_escape: false,
}))),
})),
spread: None,
})],
span: DUMMY_SP,
})),
}))),
],
}));
}));
let mut props = vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new("loadableGenerated".into(), DUMMY_SP)),
value: generated,
})))];
let mut props =
vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new("loadableGenerated".into(), DUMMY_SP)),
value: generated,
})))];
if expr.args.len() == 2 {
if let Expr::Object(ObjectLit {
props: options_props,
..
}) = &*expr.args[1].expr
{
props.extend(options_props.iter().cloned());
if expr.args.len() == 2 {
if let Expr::Object(ObjectLit {
props: options_props,
..
}) = &*expr.args[1].expr
{
props.extend(options_props.iter().cloned());
}
}
let second_arg = ExprOrSpread {
spread: None,
expr: Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
props,
})),
};
if expr.args.len() == 2 {
expr.args[1] = second_arg;
} else {
expr.args.push(second_arg)
}
self.dynamically_imported_specifier = None;
}
}
}
let second_arg = ExprOrSpread {
spread: None,
expr: Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
props,
})),
};
if expr.args.len() == 2 {
expr.args[1] = second_arg;
} else {
expr.args.push(second_arg)
}
self.dynamically_imported_specifier = None;
}
}
expr
}
expr
}
}
fn rel_filename(base: Option<&Path>, file: &FileName) -> String {
let base = match base {
Some(v) => v,
None => return file.to_string(),
};
let base = match base {
Some(v) => v,
None => return file.to_string(),
};
let file = match file {
FileName::Real(v) => v,
_ => {
return file.to_string();
}
};
let file = match file {
FileName::Real(v) => v,
_ => {
return file.to_string();
}
};
let rel_path = diff_paths(&file, base);
let rel_path = diff_paths(&file, base);
let rel_path = match rel_path {
Some(v) => v,
None => return file.display().to_string(),
};
let rel_path = match rel_path {
Some(v) => v,
None => return file.display().to_string(),
};
rel_path.display().to_string()
rel_path.display().to_string()
}

View file

@ -32,6 +32,8 @@ fn next_dynamic_errors(input: PathBuf) {
syntax(),
&|_tr| {
next_dynamic(
true,
false,
FileName::Real(PathBuf::from("/some-project/src/some-file.js")),
Some("/some-project/src".into()),
)

View file

@ -33,17 +33,33 @@ fn amp_attributes_fixture(input: PathBuf) {
#[fixture("tests/fixture/next-dynamic/**/input.js")]
fn next_dynamic_fixture(input: PathBuf) {
let output = input.parent().unwrap().join("output.js");
let output_dev = input.parent().unwrap().join("output-dev.js");
let output_prod = input.parent().unwrap().join("output-prod.js");
test_fixture(
syntax(),
&|_tr| {
next_dynamic(
true,
false,
FileName::Real(PathBuf::from("/some-project/src/some-file.js")),
Some("/some-project/src".into()),
)
},
&input,
&output,
&output_dev,
);
test_fixture(
syntax(),
&|_tr| {
next_dynamic(
false,
false,
FileName::Real(PathBuf::from("/some-project/src/some-file.js")),
Some("/some-project/src".into()),
)
},
&input,
&output_prod,
);
}

View file

@ -0,0 +1,12 @@
import dynamic1 from 'next/dynamic'
import dynamic2 from 'next/dynamic'
const DynamicComponent1 = dynamic1(() => import('../components/hello1'), {
loadableGenerated: {
modules: ['some-file.js -> ' + '../components/hello1'],
},
})
const DynamicComponent2 = dynamic2(() => import('../components/hello2'), {
loadableGenerated: {
modules: ['some-file.js -> ' + '../components/hello2'],
},
})

View file

@ -3,12 +3,10 @@ import dynamic2 from 'next/dynamic'
const DynamicComponent1 = dynamic1(() => import('../components/hello1'), {
loadableGenerated: {
webpack: () => [require.resolveWeak('../components/hello1')],
modules: ['some-file.js -> ' + '../components/hello1'],
},
})
const DynamicComponent2 = dynamic2(() => import('../components/hello2'), {
loadableGenerated: {
webpack: () => [require.resolveWeak('../components/hello2')],
modules: ['some-file.js -> ' + '../components/hello2'],
},
})

View file

@ -0,0 +1,8 @@
import dynamic from 'next/dynamic'
import somethingElse from 'something-else'
const DynamicComponent = dynamic(() => import('../components/hello'), {
loadableGenerated: {
modules: ['some-file.js -> ' + '../components/hello'],
},
})
somethingElse.dynamic('should not be transformed')

View file

@ -3,7 +3,6 @@ import somethingElse from 'something-else'
const DynamicComponent = dynamic(() => import('../components/hello'), {
loadableGenerated: {
webpack: () => [require.resolveWeak('../components/hello')],
modules: ['some-file.js -> ' + '../components/hello'],
},
})
somethingElse.dynamic('should not be transformed')

View file

@ -0,0 +1,6 @@
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/hello'), {
loadableGenerated: {
modules: ['some-file.js -> ' + '../components/hello'],
},
})

View file

@ -2,6 +2,5 @@ import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/hello'), {
loadableGenerated: {
webpack: () => [require.resolveWeak('../components/hello')],
modules: ['some-file.js -> ' + '../components/hello'],
},
})

View file

@ -0,0 +1,10 @@
import dynamic from "next/dynamic";
const DynamicComponentWithCustomLoading = dynamic(()=>import("../components/hello")
, {
loadableGenerated: {
modules: [
"some-file.js -> " + "../components/hello"
]
},
loading: ()=><p >...</p>
});

View file

@ -5,10 +5,6 @@ const DynamicComponentWithCustomLoading = dynamic(()=>import("../components/hell
webpack: ()=>[
require.resolveWeak("../components/hello")
]
,
modules: [
"some-file.js -> " + "../components/hello"
]
},
loading: ()=><p >...</p>
});

View file

@ -0,0 +1,12 @@
import dynamic from "next/dynamic";
const DynamicComponent = dynamic(()=>handleImport(import("./components/hello"))
, {
loadableGenerated: {
modules: [
"some-file.js -> " + "./components/hello"
]
},
loading: ()=>null
,
ssr: false
});

View file

@ -5,10 +5,6 @@ const DynamicComponent = dynamic(()=>handleImport(import("./components/hello"))
webpack: ()=>[
require.resolveWeak("./components/hello")
]
,
modules: [
"some-file.js -> " + "./components/hello"
]
},
loading: ()=>null
,

View file

@ -55,6 +55,7 @@ fn test(input: &Path, minify: bool) {
pages_dir: None,
is_page_file: false,
is_development: true,
is_server: false,
styled_components: Some(assert_json("{}")),
remove_console: None,
react_remove_properties: None,

View file

@ -168,29 +168,35 @@ export default function ({
options.node.properties.push(
t.objectProperty(
t.identifier('loadableGenerated'),
t.objectExpression([
t.objectProperty(
t.identifier('webpack'),
t.arrowFunctionExpression(
[],
t.arrayExpression(
dynamicImports.map((dynamicImport) => {
return t.callExpression(
t.memberExpression(
t.identifier('require'),
t.identifier('resolveWeak')
),
[dynamicImport]
t.objectExpression(
state.file.opts.caller?.isDev ||
state.file.opts.caller?.isServer
? [
t.objectProperty(
t.identifier('modules'),
t.arrayExpression(dynamicKeys)
),
]
: [
t.objectProperty(
t.identifier('webpack'),
t.arrowFunctionExpression(
[],
t.arrayExpression(
dynamicImports.map((dynamicImport) => {
return t.callExpression(
t.memberExpression(
t.identifier('require'),
t.identifier('resolveWeak')
),
[dynamicImport]
)
})
)
)
})
)
)
),
t.objectProperty(
t.identifier('modules'),
t.arrayExpression(dynamicKeys)
),
])
),
]
)
)
)

View file

@ -141,6 +141,7 @@ export function getLoaderSWCOptions({
disableNextSsg: true,
disablePageConfig: true,
isDevelopment: development,
isServer,
pagesDir,
isPageFile,
env: {
@ -165,6 +166,7 @@ export function getLoaderSWCOptions({
: {}),
disableNextSsg: !isPageFile,
isDevelopment: development,
isServer,
pagesDir,
isPageFile,
}

View file

@ -1402,6 +1402,7 @@ export default async function getBaseWebpackConfig(
runtimeAsset: hasConcurrentFeatures
? `server/${MIDDLEWARE_REACT_LOADABLE_MANIFEST}.js`
: undefined,
dev,
}),
targetWeb && new DropClientPage(),
config.outputFileTracing &&
@ -1733,6 +1734,21 @@ export default async function getBaseWebpackConfig(
devtoolRevertWarning(originalDevtool)
}
// eslint-disable-next-line no-shadow
const webpack5Config = webpackConfig as webpack5.Configuration
// disable lazy compilation of entries as next.js has it's own method here
if (webpack5Config.experiments?.lazyCompilation === true) {
webpack5Config.experiments.lazyCompilation = {
entries: false,
}
} else if (
typeof webpack5Config.experiments?.lazyCompilation === 'object' &&
webpack5Config.experiments.lazyCompilation.entries !== false
) {
webpack5Config.experiments.lazyCompilation.entries = false
}
if (typeof (webpackConfig as any).then === 'function') {
console.warn(
'> Promise returned in next config. https://nextjs.org/docs/messages/promise-in-next-config'

View file

@ -53,7 +53,8 @@ function getChunkGroupFromBlock(
function buildManifest(
_compiler: webpack.Compiler,
compilation: webpack.compilation.Compilation,
pagesDir: string
pagesDir: string,
dev: boolean
) {
let manifest: { [k: string]: { id: string | number; files: string[] } } = {}
@ -125,7 +126,7 @@ function buildManifest(
// next/dynamic so they are loaded by the same technique
// add the id and files to the manifest
const id = getModuleId(compilation, module)
const id = dev ? key : getModuleId(compilation, module)
manifest[key] = { id, files: Array.from(files) }
}
}
@ -146,19 +147,27 @@ export class ReactLoadablePlugin {
private filename: string
private pagesDir: string
private runtimeAsset?: string
private dev: boolean
constructor(opts: {
filename: string
pagesDir: string
runtimeAsset?: string
dev: boolean
}) {
this.filename = opts.filename
this.pagesDir = opts.pagesDir
this.runtimeAsset = opts.runtimeAsset
this.dev = opts.dev
}
createAssets(compiler: any, compilation: any, assets: any) {
const manifest = buildManifest(compiler, compilation, this.pagesDir)
const manifest = buildManifest(
compiler,
compilation,
this.pagesDir,
this.dev
)
// @ts-ignore: TODO: remove when webpack 5 is stable
assets[this.filename] = new sources.RawSource(
JSON.stringify(manifest, null, 2)

View file

@ -0,0 +1,40 @@
/* global __resourceQuery */
"use strict";
var urlBase = decodeURIComponent(__resourceQuery.slice(1));
exports.keepAlive = function (options) {
var data = options.data;
var onError = options.onError;
var active = options.active;
var module = options.module;
var response;
var request = (
urlBase.startsWith("https") ? require("https") : require("http")
).request(
urlBase + data,
{
agent: false,
headers: { accept: "text/event-stream" }
},
function (res) {
response = res;
response.on("error", errorHandler);
if (!active && !module.hot) {
console.log(
"Hot Module Replacement is not enabled. Waiting for process restart..."
);
}
}
);
function errorHandler(err) {
err.message =
"Problem communicating active modules to the server: " + err.message;
onError(err);
}
request.on("error", errorHandler);
request.end();
return function () {
response.destroy();
};
};

View file

@ -0,0 +1,74 @@
/* global __resourceQuery */
"use strict";
if (typeof EventSource !== "function") {
throw new Error(
"Environment doesn't support lazy compilation (requires EventSource)"
);
}
var urlBase = decodeURIComponent(__resourceQuery.slice(1));
var activeEventSource;
var activeKeys = new Map();
var errorHandlers = new Set();
var updateEventSource = function updateEventSource() {
if (activeEventSource) activeEventSource.close();
if (activeKeys.size) {
activeEventSource = new EventSource(
urlBase + Array.from(activeKeys.keys()).join("@")
);
activeEventSource.onerror = function (event) {
errorHandlers.forEach(function (onError) {
onError(
new Error(
"Problem communicating active modules to the server: " +
event.message +
" " +
event.filename +
":" +
event.lineno +
":" +
event.colno +
" " +
event.error
)
);
});
};
} else {
activeEventSource = undefined;
}
};
exports.keepAlive = function (options) {
var data = options.data;
var onError = options.onError;
var active = options.active;
var module = options.module;
errorHandlers.add(onError);
var value = activeKeys.get(data) || 0;
activeKeys.set(data, value + 1);
if (value === 0) {
updateEventSource();
}
if (!active && !module.hot) {
console.log(
"Hot Module Replacement is not enabled. Waiting for process restart..."
);
}
return function () {
errorHandlers.delete(onError);
setTimeout(function () {
var value = activeKeys.get(data);
if (value === 1) {
activeKeys.delete(data);
updateEventSource();
} else {
activeKeys.set(data, value - 1);
}
}, 1000);
};
};

View file

@ -1,67 +1,6 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 685:
/***/ (function(module) {
module.exports = require("http");
/***/ }),
/***/ 687:
/***/ (function(module) {
module.exports = require("https");
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __nccwpck_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ var threw = true;
/******/ try {
/******/ __webpack_modules__[moduleId](module, module.exports, __nccwpck_require__);
/******/ threw = false;
/******/ } finally {
/******/ if(threw) delete __webpack_module_cache__[moduleId];
/******/ }
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat */
/******/
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
!function() {
var exports = __webpack_exports__;
var __resourceQuery = "";
/* global __resourceQuery */
"use strict";
var urlBase = decodeURIComponent(__resourceQuery.slice(1));
exports.keepAlive = function (options) {
@ -71,7 +10,7 @@ exports.keepAlive = function (options) {
var module = options.module;
var response;
var request = (
urlBase.startsWith("https") ? __nccwpck_require__(687) : __nccwpck_require__(685)
urlBase.startsWith("https") ? require("https") : require("http")
).request(
urlBase + data,
{
@ -99,8 +38,3 @@ exports.keepAlive = function (options) {
response.destroy();
};
};
}();
module.exports = __webpack_exports__;
/******/ })()
;

View file

@ -1,18 +1,6 @@
/******/ (function() { // webpackBootstrap
/******/ "use strict";
/******/ /* webpack/runtime/compat */
/******/
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
!function() {
var exports = __webpack_exports__;
var __resourceQuery = "";
/* global __resourceQuery */
"use strict";
if (typeof EventSource !== "function") {
throw new Error(
@ -84,8 +72,3 @@ exports.keepAlive = function (options) {
}, 1000);
};
};
}();
module.exports = __webpack_exports__;
/******/ })()
;

View file

@ -95,21 +95,17 @@ function createLoadableComponent(loadFn, options) {
}
// Client only
if (
!initialized &&
typeof window !== 'undefined' &&
typeof opts.webpack === 'function' &&
typeof require.resolveWeak === 'function' &&
!opts.suspense
) {
const moduleIds = opts.webpack()
READY_INITIALIZERS.push((ids) => {
for (const moduleId of moduleIds) {
if (ids.indexOf(moduleId) !== -1) {
return init()
if (!initialized && typeof window !== 'undefined' && !opts.suspense) {
const moduleIds = opts.webpack ? opts.webpack() : opts.modules
if (moduleIds) {
READY_INITIALIZERS.push((ids) => {
for (const moduleId of moduleIds) {
if (ids.indexOf(moduleId) !== -1) {
return init()
}
}
}
})
})
}
}
function LoadableImpl(props, ref) {

View file

@ -1007,7 +1007,6 @@ export async function ncc(task, opts) {
'ncc_unistore',
'ncc_web_vitals',
'ncc_webpack_bundle5',
'ncc_webpack_bundle_packages',
'ncc_webpack_sources1',
'ncc_webpack_sources3',
'ncc_ws',
@ -1021,6 +1020,7 @@ export async function ncc(task, opts) {
],
opts
)
await task.parallel(['ncc_webpack_bundle_packages'], opts)
await task.parallel(['ncc_babel_bundle_packages'], opts)
await task.parallel(['copy_react_server_dom_webpack'])
}

View file

@ -0,0 +1,8 @@
copy node_modules\webpack5\lib\hmr\HotModuleReplacement.runtime.js packages\next\bundles\webpack\packages\
copy node_modules\webpack5\lib\hmr\JavascriptHotModuleReplacement.runtime.js packages\next\bundles\webpack\packages\
copy node_modules\webpack5\hot\lazy-compilation-node.js packages\next\bundles\webpack\packages\
copy node_modules\webpack5\hot\lazy-compilation-web.js packages\next\bundles\webpack\packages\
yarn --cwd packages/next ncc-compiled
rem Make sure to exit with 1 if there are changes after running ncc-compiled
rem step to ensure we get any changes committed

View file

@ -2,6 +2,8 @@
cp node_modules/webpack5/lib/hmr/HotModuleReplacement.runtime.js packages/next/bundles/webpack/packages/
cp node_modules/webpack5/lib/hmr/JavascriptHotModuleReplacement.runtime.js packages/next/bundles/webpack/packages/
cp node_modules/webpack5/hot/lazy-compilation-node.js packages/next/bundles/webpack/packages/
cp node_modules/webpack5/hot/lazy-compilation-web.js packages/next/bundles/webpack/packages/
yarn --cwd packages/next ncc-compiled
# Make sure to exit with 1 if there are changes after running ncc-compiled

View file

@ -32,7 +32,7 @@ describe('basic next/dynamic usage', () => {
const $ = await get$('/docs/dynamic/ssr')
// Make sure the client side knows it has to wait for the bundle
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
'./components/hello1.js'
'dynamic/ssr.js -> ../../components/hello1'
)
expect($('body').text()).toMatch(/Hello World 1/)
})
@ -41,7 +41,7 @@ describe('basic next/dynamic usage', () => {
const $ = await get$('/docs/dynamic/function')
// Make sure the client side knows it has to wait for the bundle
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
'./components/hello1.js'
'dynamic/function.js -> ../../components/hello1'
)
expect($('body').text()).toMatch(/Hello World 1/)
})

View file

@ -29,7 +29,7 @@ describe('basic next/dynamic usage', () => {
const $ = await get$('/dynamic/ssr')
// Make sure the client side knows it has to wait for the bundle
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
'./components/hello1.js'
'dynamic/ssr.js -> ../../components/hello1'
)
expect($('body').text()).toMatch(/Hello World 1/)
})
@ -38,7 +38,7 @@ describe('basic next/dynamic usage', () => {
const $ = await get$('/dynamic/function')
// Make sure the client side knows it has to wait for the bundle
expect(JSON.parse($('#__NEXT_DATA__').html()).dynamicIds).toContain(
'./components/hello1.js'
'dynamic/function.js -> ../../components/hello1'
)
expect($('body').text()).toMatch(/Hello World 1/)
})

View file

@ -7,7 +7,6 @@ import { killApp, findPort, launchApp, check } from 'next-test-utils'
const appDir = join(__dirname, '../')
const appPage = join(appDir, 'pages/_app.js')
const indexPage = join(appDir, 'pages/index.js')
const documentPage = join(appDir, 'pages/_document.js')
let appPort
@ -21,10 +20,8 @@ describe('_app/_document add HMR', () => {
afterAll(() => killApp(app))
it('should HMR when _app is added', async () => {
let indexContent = await fs.readFile(indexPage)
const browser = await webdriver(appPort, '/')
try {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.documentElement.innerHTML')
expect(html).not.toContain('custom _app')
expect(html).toContain('index page')
@ -50,16 +47,19 @@ describe('_app/_document add HMR', () => {
: html
}, 'success')
} finally {
await fs.writeFile(indexPage, indexContent)
await fs.remove(appPage)
await check(async () => {
const html = await browser.eval('document.documentElement.innerHTML')
return !html.includes('custom _app') && html.includes('index page')
? 'restored'
: html
}, 'restored')
}
})
it('should HMR when _document is added', async () => {
let indexContent = await fs.readFile(indexPage)
const browser = await webdriver(appPort, '/')
try {
const browser = await webdriver(appPort, '/')
const html = await browser.eval('document.documentElement.innerHTML')
expect(html).not.toContain('custom _document')
expect(html).toContain('index page')
@ -101,8 +101,13 @@ describe('_app/_document add HMR', () => {
: html
}, 'success')
} finally {
await fs.writeFile(indexPage, indexContent)
await fs.remove(documentPage)
await check(async () => {
const html = await browser.eval('document.documentElement.innerHTML')
return !html.includes('custom _document') && html.includes('index page')
? 'restored'
: html
}, 'restored')
}
})
})

View file

@ -0,0 +1,3 @@
{
"presets": ["next/babel"]
}

View file

@ -0,0 +1 @@
export default 'foobar'

View file

@ -0,0 +1,3 @@
export default () => {
return '4'
}

View file

@ -0,0 +1,6 @@
import something from '../apples'
export default () => {
// have to do something with module so it is not tree shaken
console.log(something)
return '1'
}

View file

@ -0,0 +1,3 @@
export default () => {
return '3'
}

View file

@ -0,0 +1,6 @@
import something from '../apples'
export default () => {
// have to do something with module so it is not tree shaken
console.log(something)
return '2'
}

View file

@ -0,0 +1,8 @@
module.exports = {
webpack(config, { isServer, dev }) {
if (!isServer && dev) {
config.experiments.lazyCompilation = true
}
return config
},
}

View file

@ -0,0 +1,48 @@
import dynamic from 'next/dynamic'
import { useState, useEffect } from 'react'
import FourDirect from '../components/four'
const One = dynamic(() => import('../components/one'))
const Two = dynamic(() => import('../components/two'))
const Three = dynamic(() => import('../components/three'))
const Four = dynamic(() => import('../components/four'))
if (typeof window !== 'undefined') {
window.caughtErrors = ''
const origError = console.error
console.error = function (...args) {
window.caughtErrors += args.join(' ')
origError(...args)
}
}
const BEFORE_HYDRATION =
typeof document !== 'undefined' && document.getElementById('foo').innerHTML
const Index = () => {
const [firstRender, setFirstRender] = useState('the-server-value')
const [beforeHydration, setBeforeHydration] = useState(
'the-second-server-value'
)
useEffect(() => {
setFirstRender(document.getElementById('foo').innerHTML)
setBeforeHydration(BEFORE_HYDRATION)
}, [])
return (
<>
<div id="foo">
Index
<One />
<Two />
<Three />
<Four />
<FourDirect />
</div>
<div id="first-render">{firstRender}</div>
<div id="before-hydration">{beforeHydration}</div>
</>
)
}
export default Index

View file

@ -0,0 +1,73 @@
/* eslint-env jest */
import webdriver from 'next-webdriver'
import { join } from 'path'
import {
renderViaHTTP,
findPort,
launchApp,
killApp,
runNextCommand,
nextServer,
startApp,
stopApp,
} from 'next-test-utils'
let app
let appPort
let server
const appDir = join(__dirname, '../')
function runTests() {
it('should render server value', async () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/the-server-value/i)
expect(html).toMatch(/the-second-server-value/i)
})
it('should render dynamic server rendered values before hydration', async () => {
const browser = await webdriver(appPort, '/')
const text = await browser.elementByCss('#before-hydration').text()
expect(text).toBe('Index<!-- -->1<!-- -->2<!-- -->3<!-- -->4<!-- -->4')
expect(await browser.eval('window.caughtErrors')).toBe('')
})
it('should render dynamic server rendered values on client mount', async () => {
const browser = await webdriver(appPort, '/')
const text = await browser.elementByCss('#first-render').text()
expect(text).toBe('Index<!-- -->1<!-- -->2<!-- -->3<!-- -->4<!-- -->4')
expect(await browser.eval('window.caughtErrors')).toBe('')
})
}
describe('next/dynamic', () => {
describe('dev mode', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))
runTests(true)
})
describe('production mode', () => {
beforeAll(async () => {
await runNextCommand(['build', appDir])
app = nextServer({
dir: appDir,
dev: false,
quiet: true,
})
server = await startApp(app)
appPort = server.address().port
})
afterAll(() => stopApp(server))
runTests()
})
})

View file

@ -193,7 +193,7 @@ describe('next-babel-loader', () => {
expect(
code.replace(/modules: \[".*?"/, 'modules:["/path/to/page"')
).toMatchInlineSnapshot(
`"var _jsxFileName = \\"index.js\\";import React from \\"react\\";var __jsx = React.createElement;import dynamic from 'next/dynamic';const Comp = dynamic(() => import('comp'), { loadableGenerated: { webpack: () => [require.resolveWeak('comp')], modules:[\\"/path/to/page\\" + 'comp'] }});export default function Page(props) { return __jsx(Comp, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 7, columnNumber: 18 } });}"`
`"var _jsxFileName = \\"index.js\\";import React from \\"react\\";var __jsx = React.createElement;import dynamic from 'next/dynamic';const Comp = dynamic(() => import('comp'), { loadableGenerated: { webpack: () => [require.resolveWeak('comp')] }});export default function Page(props) { return __jsx(Comp, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 7, columnNumber: 18 } });}"`
)
})