Skip to content

Commit 9cfa68f

Browse files
committed
feat: add --dynamic flag to speed up building during development
1 parent 55f10c2 commit 9cfa68f

File tree

14 files changed

+3119
-1574
lines changed

14 files changed

+3119
-1574
lines changed

Diff for: .eslintrc

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
{
2-
"extends": "mo/esnext"
2+
"extends": "mo/esnext",
3+
"parser": "babel-eslint",
4+
"parserOptions": {
5+
"allowImportExportEverywhere": true
6+
}
37
}

Diff for: examples/simple/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": "1.0.0",
55
"description": "",
66
"scripts": {
7-
"dev": "node ../../packages/cli/bin/index.js --single-page=pages/home/home",
7+
"dev": "node ../../packages/cli/bin/index.js --dynamic",
88
"build": "node ../../packages/cli/bin/index.js --prod"
99
},
1010
"keywords": [],

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"devDependencies": {
5050
"@commitlint/cli": "8.0.0",
5151
"@commitlint/config-conventional": "8.0.0",
52+
"babel-eslint": "^10.0.2",
5253
"eslint": "6.0.1",
5354
"eslint-config-mo": "0.5.2",
5455
"gh-pages": "2.0.1",

Diff for: packages/cli/bin/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cli
1818
.command( '', 'Build in development mode' )
1919
.option( '--prod', 'Build in production mode' )
2020
.option( '--single-page <page>', 'Build single page to speed up' )
21+
.option( '--dynamic' )
2122
.action( options => {
2223
options = normalizeCliOptions( options )
2324

Diff for: packages/cli/lib/commands/dev.js

+91-16
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,12 @@ async function dev( cliOptions = {} ) {
3939
const host = nutConfig.host || DEFAULT_HOST
4040
const port = nutConfig.port || DEFAULT_PORT
4141

42-
let devServerOptions = {
43-
contentBase: './dist',
44-
host,
45-
hot: true,
46-
quiet: true,
47-
headers: {
48-
'Access-Control-Allow-Origin': '*',
49-
},
50-
historyApiFallback: true,
51-
}
52-
53-
if ( nutConfig.devServer ) {
54-
devServerOptions = Object.assign( devServerOptions, nutConfig.devServer )
55-
}
42+
const dynamicPages = []
5643

5744
const modules = await generateVirtualModules( nutConfig, {
5845
env: 'dev',
5946
cliOptions,
47+
dynamicPages,
6048
} )
6149

6250
let virtualModules
@@ -81,6 +69,12 @@ async function dev( cliOptions = {} ) {
8169
return virtualModules
8270
} )
8371
.use( VirtualModulesPlugin, [ modules ] )
72+
webpackConfig.plugin( 'define' )
73+
.use( webpack.DefinePlugin, [
74+
{
75+
NUT_CLI_DYNAMIC: JSON.stringify( Boolean( cliOptions.dynamic ) )
76+
}
77+
] )
8478

8579
applyCSSRules( webpackConfig, 'dev', appId )
8680

@@ -100,15 +94,95 @@ async function dev( cliOptions = {} ) {
10094
)
10195
}
10296

103-
WebpackDevServer.addDevServerEntrypoints( finalWebpackConfig, devServerOptions )
10497
const compiler = webpack( finalWebpackConfig )
105-
const server = new WebpackDevServer( compiler, devServerOptions )
10698

10799
compiler.hooks.done.tap( 'memory-usage', () => {
108100
const { heapUsed } = process.memoryUsage()
109101
console.log( chalk.gray( `\n${ prettyBytes( heapUsed ) } Memory Used\n` ) )
110102
} )
111103

104+
const waitCallbacks = []
105+
compiler.hooks.done.tap( 'wait-until-valid', () => {
106+
let callback = waitCallbacks.shift()
107+
while ( callback ) {
108+
callback()
109+
callback = waitCallbacks.shift()
110+
}
111+
} )
112+
113+
function waitUntilValid() {
114+
const deferred = {}
115+
116+
deferred.promise = new Promise( ( resolve, reject ) => {
117+
deferred.resolve = resolve
118+
deferred.reject = reject
119+
} )
120+
121+
waitCallbacks.push( deferred.resolve )
122+
123+
return deferred.promise
124+
}
125+
126+
let devServerOptions = {
127+
contentBase: './dist',
128+
host,
129+
hot: true,
130+
quiet: true,
131+
headers: {
132+
'Access-Control-Allow-Origin': '*',
133+
},
134+
historyApiFallback: true,
135+
before( app ) {
136+
app.get( `/_nut_dynamic_build_page`, async ( req, res ) => {
137+
const page = req.query.page
138+
139+
if ( !cliOptions.dynamic ) {
140+
return res.json( {
141+
success: true,
142+
waitHotApply: false,
143+
} )
144+
}
145+
146+
// prevent waiting for valid
147+
if ( dynamicPages.includes( page ) ) {
148+
return res.json( {
149+
success: true,
150+
waitHotApply: false,
151+
} )
152+
}
153+
154+
dynamicPages.push( page )
155+
156+
const modules = await generateVirtualModules( nutConfig, {
157+
env: 'dev',
158+
cliOptions,
159+
dynamicPages,
160+
} )
161+
162+
for ( const [ path, content ] of Object.entries( modules ) ) {
163+
virtualModules.writeModule(
164+
path,
165+
content
166+
)
167+
}
168+
169+
await waitUntilValid()
170+
171+
res.json( {
172+
success: true,
173+
waitHotApply: true,
174+
} )
175+
} )
176+
},
177+
}
178+
179+
if ( nutConfig.devServer ) {
180+
devServerOptions = Object.assign( devServerOptions, nutConfig.devServer )
181+
}
182+
183+
WebpackDevServer.addDevServerEntrypoints( finalWebpackConfig, devServerOptions )
184+
const server = new WebpackDevServer( compiler, devServerOptions )
185+
112186
server.listen( port, host, () => {
113187
const routerMode = ( nutConfig.router && nutConfig.router.mode ) || 'hash'
114188

@@ -201,6 +275,7 @@ async function dev( cliOptions = {} ) {
201275
const modules = await generateVirtualModules( nutConfig, {
202276
env: 'dev',
203277
cliOptions,
278+
dynamicPages,
204279
} )
205280

206281
for ( const [ path, content ] of Object.entries( modules ) ) {

Diff for: packages/cli/lib/commands/prod.js

+7
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ async function productionify( webpackConfig, config, appId ) {
9696

9797
webpackConfig.plugin( 'virtual-modules' )
9898
.use( VirtualModulesPlugin, [ modules ] )
99+
100+
webpackConfig.plugin( 'define' )
101+
.use( webpack.DefinePlugin, [
102+
{
103+
NUT_CLI_DYNAMIC: JSON.stringify( false )
104+
}
105+
] )
99106
}
100107

101108
async function prod() {

Diff for: packages/cli/lib/runtime/core/router.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import qs from 'query-string'
22
import normalizeRoute from '../utils/normalize-route'
3+
import dynamicBuild from '../utils/dynamic-build'
34

45
export default function ( pages, rootRouter ) {
56
return {
@@ -82,22 +83,36 @@ export default function ( pages, rootRouter ) {
8283
return url
8384
},
8485

85-
push( route = '', options = {}, callback ) {
86+
async push( route = '', options = {}, callback ) {
8687
const path = this.format( route, options )
8788
if ( !path ) {
8889
return
8990
}
91+
92+
await dynamicBuild( this.matchPage( path ) )
93+
9094
rootRouter.push( path, callback )
9195
},
9296

93-
replace( route = '', options = {}, callback ) {
97+
async replace( route = '', options = {}, callback ) {
9498
const path = this.format( route, options )
9599
if ( !path ) {
96100
return
97101
}
102+
103+
await dynamicBuild( this.matchPage( path ) )
104+
98105
rootRouter.replace( path, callback )
99106
},
100107

108+
matchPage( ...args ) {
109+
const matched = rootRouter.match( ...args )
110+
111+
if ( matched ) {
112+
return matched && matched.options && matched.options.page
113+
}
114+
},
115+
101116
match( ...args ) {
102117
return rootRouter.match( ...args )
103118
},

Diff for: packages/cli/lib/runtime/entries/single.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import registerLayouts from '../steps/register-layouts'
2121

2222
import getFirstRoute from '../utils/get-first-route'
2323
import switchTheme from '../utils/switch-theme'
24+
// fix __webpack_require__.e is not defined in dynamic build mode
25+
import '../utils/add-require-ensure'
26+
import dynamicBuild from '../utils/dynamic-build'
2427

2528
import app from '@/nut-auto-generated-app'
2629
import createAPI from '../context/api'
@@ -109,22 +112,26 @@ import use from '../context/use'
109112
}
110113
}
111114

115+
const matched = rootRouter.match()
116+
const matchedPage = matched && matched.options && matched.options.page
117+
118+
await dynamicBuild( matchedPage )
119+
112120
nico.start( '#app' )
113121
events.emit( 'route:enabled', context )
114122

115-
const matched = rootRouter.match()
116123
if ( !matched || ( matched.router === rootRouter ) ) {
117124
const homeMatched = rootRouter.match( '/' )
118125

119126
if (
120127
homeMatched &&
121128
( homeMatched.router !== rootRouter )
122129
) {
123-
rootRouter.push( '/' )
130+
context.api.router.push( '/' )
124131
} else {
125132
const firstRoute = getFirstRoute( context )
126133
if ( firstRoute ) {
127-
rootRouter.push( firstRoute )
134+
context.api.router.push( firstRoute )
128135
}
129136
}
130137
}

Diff for: packages/cli/lib/runtime/utils/add-require-ensure.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default () => {
2+
return import( './dynamic-import-fixture' )
3+
}

Diff for: packages/cli/lib/runtime/utils/dynamic-build.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* global NUT_CLI_DYNAMIC, fetch */
2+
function onHMRStatusTransferTo( condition = () => false, callback = () => {} ) {
3+
if ( module.hot ) {
4+
/* eslint-disable */
5+
function handler( status ) {
6+
if ( condition( status ) === true ) {
7+
module.hot.removeStatusHandler( handler )
8+
callback()
9+
}
10+
}
11+
/* eslint-enable */
12+
13+
module.hot.addStatusHandler( handler )
14+
}
15+
}
16+
17+
// trigger dynamic build before route lifecycle enter
18+
async function dynamicBuild( page ) {
19+
if ( !NUT_CLI_DYNAMIC || ( process.env.NODE_ENV === 'production' ) ) {
20+
return
21+
}
22+
23+
if ( module.hot && page ) {
24+
const promise = new Promise( resolve => {
25+
if ( module.hot.status() === 'idle' ) {
26+
onHMRStatusTransferTo(
27+
status => status !== 'idle',
28+
() => {
29+
onHMRStatusTransferTo(
30+
status => status === 'idle',
31+
() => {
32+
resolve()
33+
}
34+
)
35+
}
36+
)
37+
} else {
38+
onHMRStatusTransferTo(
39+
status => status === 'idle',
40+
resolve
41+
)
42+
}
43+
} )
44+
45+
let json
46+
try {
47+
const response = await fetch( `/_nut_dynamic_build_page?page=${
48+
encodeURIComponent( page )
49+
}` )
50+
json = await response.json()
51+
} catch ( e ) {
52+
console.log( e )
53+
}
54+
55+
if ( json.success && ( json.waitHotApply === true ) ) {
56+
await promise
57+
}
58+
}
59+
}
60+
61+
export default dynamicBuild
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default () => {}

0 commit comments

Comments
 (0)