Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
CoCo_Code_Op2
next.js
提交
fb81ecb2
N
next.js
项目概览
CoCo_Code_Op2
/
next.js
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
N
next.js
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
fb81ecb2
编写于
7月 28, 2020
作者:
P
Prateek Bhatnagar
提交者:
GitHub
7月 28, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Font optimizations (#14746)
Co-authored-by:
N
atcastle
<
atcastle@gmail.com
>
上级
27c207da
变更
23
隐藏空白更改
内联
并排
Showing
23 changed file
with
853 addition
and
12 deletion
+853
-12
packages/next/build/webpack-config.ts
packages/next/build/webpack-config.ts
+8
-1
packages/next/build/webpack/loaders/next-serverless-loader.ts
...ages/next/build/webpack/loaders/next-serverless-loader.ts
+12
-1
packages/next/build/webpack/plugins/font-stylesheet-gathering-plugin.ts
...build/webpack/plugins/font-stylesheet-gathering-plugin.ts
+141
-0
packages/next/export/index.ts
packages/next/export/index.ts
+1
-0
packages/next/export/worker.ts
packages/next/export/worker.ts
+33
-2
packages/next/next-server/lib/constants.ts
packages/next/next-server/lib/constants.ts
+2
-0
packages/next/next-server/lib/head.tsx
packages/next/next-server/lib/head.tsx
+15
-0
packages/next/next-server/lib/post-process.ts
packages/next/next-server/lib/post-process.ts
+161
-0
packages/next/next-server/server/config.ts
packages/next/next-server/server/config.ts
+1
-0
packages/next/next-server/server/font-utils.ts
packages/next/next-server/server/font-utils.ts
+59
-0
packages/next/next-server/server/next-server.ts
packages/next/next-server/server/next-server.ts
+22
-2
packages/next/next-server/server/render.tsx
packages/next/next-server/server/render.tsx
+22
-0
packages/next/next-server/server/require.ts
packages/next/next-server/server/require.ts
+10
-0
packages/next/package.json
packages/next/package.json
+2
-1
packages/next/pages/_document.tsx
packages/next/pages/_document.tsx
+30
-4
test/integration/build-output/test/index.test.js
test/integration/build-output/test/index.test.js
+1
-1
test/integration/font-optimization/pages/_document.js
test/integration/font-optimization/pages/_document.js
+30
-0
test/integration/font-optimization/pages/index.js
test/integration/font-optimization/pages/index.js
+7
-0
test/integration/font-optimization/pages/stars.js
test/integration/font-optimization/pages/stars.js
+26
-0
test/integration/font-optimization/pages/static-head.js
test/integration/font-optimization/pages/static-head.js
+18
-0
test/integration/font-optimization/server.js
test/integration/font-optimization/server.js
+111
-0
test/integration/font-optimization/test/index.test.js
test/integration/font-optimization/test/index.test.js
+129
-0
yarn.lock
yarn.lock
+12
-0
未找到文件。
packages/next/build/webpack-config.ts
浏览文件 @
fb81ecb2
...
...
@@ -53,7 +53,7 @@ import WebpackConformancePlugin, {
ReactSyncScriptsConformanceCheck
,
}
from
'
./webpack/plugins/webpack-conformance-plugin
'
import
{
WellKnownErrorsPlugin
}
from
'
./webpack/plugins/wellknown-errors-plugin
'
import
FontStylesheetGatheringPlugin
from
'
./webpack/plugins/font-stylesheet-gathering-plugin
'
type
ExcludesFalse
=
<
T
>
(
x
:
T
|
false
)
=>
x
is
T
const
isWebpack5
=
parseInt
(
webpack
.
version
!
)
===
5
...
...
@@ -873,6 +873,9 @@ export default async function getBaseWebpackConfig(
'
process.env.__NEXT_REACT_MODE
'
:
JSON
.
stringify
(
config
.
experimental
.
reactMode
),
'
process.env.__NEXT_OPTIMIZE_FONTS
'
:
JSON
.
stringify
(
config
.
experimental
.
optimizeFonts
),
'
process.env.__NEXT_SCROLL_RESTORATION
'
:
JSON
.
stringify
(
config
.
experimental
.
scrollRestoration
),
...
...
@@ -978,6 +981,10 @@ export default async function getBaseWebpackConfig(
inputChunkName
.
replace
(
/
\.
js$/
,
'
.module.js
'
),
})
})(),
config
.
experimental
.
optimizeFonts
&&
!
dev
&&
isServer
&&
new
FontStylesheetGatheringPlugin
(),
config
.
experimental
.
conformance
&&
!
isWebpack5
&&
!
dev
&&
...
...
packages/next/build/webpack/loaders/next-serverless-loader.ts
浏览文件 @
fb81ecb2
...
...
@@ -6,6 +6,7 @@ import { loader } from 'webpack'
import
{
API_ROUTE
}
from
'
../../../lib/constants
'
import
{
BUILD_MANIFEST
,
FONT_MANIFEST
,
REACT_LOADABLE_MANIFEST
,
ROUTES_MANIFEST
,
}
from
'
../../../next-server/lib/constants
'
...
...
@@ -58,6 +59,10 @@ const nextServerlessLoader: loader.Loader = function () {
'
/
'
)
const
routesManifest
=
join
(
distDir
,
ROUTES_MANIFEST
).
replace
(
/
\\
/g
,
'
/
'
)
const
fontManifest
=
join
(
distDir
,
'
serverless
'
,
FONT_MANIFEST
).
replace
(
/
\\
/g
,
'
/
'
)
const
escapedBuildId
=
escapeRegexp
(
buildId
)
const
pageIsDynamicRoute
=
isDynamicRoute
(
page
)
...
...
@@ -266,7 +271,7 @@ const nextServerlessLoader: loader.Loader = function () {
}
const {parse} = require('url')
const {parse: parseQs} = require('querystring')
const {
renderToHTML
} = require('next/dist/next-server/server/render');
const {
renderToHTML
} = require('next/dist/next-server/server/render');
const { tryGetPreviewData } = require('next/dist/next-server/server/api-utils');
const {sendPayload} = require('next/dist/next-server/server/send-payload');
const buildManifest = require('
${
buildManifest
}
');
...
...
@@ -274,6 +279,7 @@ const nextServerlessLoader: loader.Loader = function () {
const Document = require('
${
absoluteDocumentPath
}
').default;
const Error = require('
${
absoluteErrorPath
}
').default;
const App = require('
${
absoluteAppPath
}
').default;
${
dynamicRouteImports
}
${
rewriteImports
}
...
...
@@ -418,6 +424,11 @@ const nextServerlessLoader: loader.Loader = function () {
const previewData = tryGetPreviewData(req, res, options.previewProps)
const isPreviewMode = previewData !== false
if (process.env.__NEXT_OPTIMIZE_FONTS) {
renderOpts.optimizeFonts = true
renderOpts.fontManifest = require('
${
fontManifest
}
')
process.env['__NEXT_OPTIMIZE_FONT'+'S'] = true
}
let result = await renderToHTML(req, res, "
${
page
}
", Object.assign({}, getStaticProps ? { ...(parsedUrl.query.amp ? { amp: '1' } : {}) } : parsedUrl.query, nowParams ? nowParams : params, _params, isFallback ? { __nextFallback: 'true' } : {}), renderOpts)
if (!renderMode) {
...
...
packages/next/build/webpack/plugins/font-stylesheet-gathering-plugin.ts
0 → 100644
浏览文件 @
fb81ecb2
// eslint-disable-next-line import/no-extraneous-dependencies
import
{
NodePath
}
from
'
ast-types/lib/node-path
'
import
{
compilation
as
CompilationType
,
Compiler
}
from
'
webpack
'
import
{
namedTypes
}
from
'
ast-types
'
import
{
RawSource
}
from
'
webpack-sources
'
import
{
getFontDefinitionFromNetwork
,
FontManifest
,
}
from
'
../../../next-server/server/font-utils
'
// @ts-ignore
import
BasicEvaluatedExpression
from
'
webpack/lib/BasicEvaluatedExpression
'
import
{
OPTIMIZED_FONT_PROVIDERS
}
from
'
../../../next-server/lib/constants
'
interface
VisitorMap
{
[
key
:
string
]:
(
path
:
NodePath
)
=>
void
}
export
default
class
FontStylesheetGatheringPlugin
{
compiler
?:
Compiler
gatheredStylesheets
:
Array
<
string
>
=
[]
private
parserHandler
=
(
factory
:
CompilationType
.
NormalModuleFactory
):
void
=>
{
const
JS_TYPES
=
[
'
auto
'
,
'
esm
'
,
'
dynamic
'
]
// Do an extra walk per module and add interested visitors to the walk.
for
(
const
type
of
JS_TYPES
)
{
factory
.
hooks
.
parser
.
for
(
'
javascript/
'
+
type
)
.
tap
(
this
.
constructor
.
name
,
(
parser
:
any
)
=>
{
/**
* Webpack fun facts:
* `parser.hooks.call.for` cannot catch calls for user defined identifiers like `__jsx`
* it can only detect calls for native objects like `window`, `this`, `eval` etc.
* In order to be able to catch calls of variables like `__jsx`, first we need to catch them as
* Identifier and then return `BasicEvaluatedExpression` whose `id` and `type` webpack matches to
* invoke hook for call.
* See: https://github.com/webpack/webpack/blob/webpack-4/lib/Parser.js#L1931-L1932.
*/
parser
.
hooks
.
evaluate
.
for
(
'
Identifier
'
)
.
tap
(
this
.
constructor
.
name
,
(
node
:
namedTypes
.
Identifier
)
=>
{
// We will only optimize fonts from first party code.
if
(
parser
?.
state
?.
module
?.
resource
.
includes
(
'
node_modules
'
))
{
return
}
return
node
.
name
===
'
__jsx
'
?
new
BasicEvaluatedExpression
()
//@ts-ignore
.
setRange
(
node
.
range
)
.
setExpression
(
node
)
.
setIdentifier
(
'
__jsx
'
)
:
undefined
})
parser
.
hooks
.
call
.
for
(
'
__jsx
'
)
.
tap
(
this
.
constructor
.
name
,
(
node
:
namedTypes
.
CallExpression
)
=>
{
if
(
node
.
arguments
.
length
!==
2
)
{
// A font link tag has only two arguments rel=stylesheet and href='...'
return
}
if
(
!
isNodeCreatingLinkElement
(
node
))
{
return
}
// node.arguments[0] is the name of the tag and [1] are the props.
const
propsNode
=
node
.
arguments
[
1
]
as
namedTypes
.
ObjectExpression
const
props
:
{
[
key
:
string
]:
string
}
=
{}
propsNode
.
properties
.
forEach
((
prop
)
=>
{
if
(
prop
.
type
!==
'
Property
'
)
{
return
}
if
(
prop
.
key
.
type
===
'
Identifier
'
&&
prop
.
value
.
type
===
'
Literal
'
)
{
props
[
prop
.
key
.
name
]
=
prop
.
value
.
value
as
string
}
})
if
(
!
props
.
rel
||
props
.
rel
!==
'
stylesheet
'
||
!
props
.
href
||
!
OPTIMIZED_FONT_PROVIDERS
.
some
((
url
)
=>
props
.
href
.
startsWith
(
url
)
)
)
{
return
false
}
this
.
gatheredStylesheets
.
push
(
props
.
href
)
})
})
}
}
public
apply
(
compiler
:
Compiler
)
{
this
.
compiler
=
compiler
compiler
.
hooks
.
normalModuleFactory
.
tap
(
this
.
constructor
.
name
,
this
.
parserHandler
)
compiler
.
hooks
.
make
.
tapAsync
(
this
.
constructor
.
name
,
(
compilation
,
cb
)
=>
{
compilation
.
hooks
.
finishModules
.
tapAsync
(
this
.
constructor
.
name
,
async
(
_
:
any
,
modulesFinished
:
Function
)
=>
{
const
fontDefinitionPromises
=
this
.
gatheredStylesheets
.
map
((
url
)
=>
getFontDefinitionFromNetwork
(
url
)
)
let
manifestContent
:
FontManifest
=
[]
for
(
let
promiseIndex
in
fontDefinitionPromises
)
{
manifestContent
.
push
({
url
:
this
.
gatheredStylesheets
[
promiseIndex
],
content
:
await
fontDefinitionPromises
[
promiseIndex
],
})
}
compilation
.
assets
[
'
font-manifest.json
'
]
=
new
RawSource
(
JSON
.
stringify
(
manifestContent
,
null
,
'
'
)
)
modulesFinished
()
}
)
cb
()
})
}
}
function
isNodeCreatingLinkElement
(
node
:
namedTypes
.
CallExpression
)
{
const
callee
=
node
.
callee
as
namedTypes
.
Identifier
if
(
callee
.
type
!==
'
Identifier
'
)
{
return
false
}
const
componentNode
=
node
.
arguments
[
0
]
as
namedTypes
.
Literal
if
(
componentNode
.
type
!==
'
Literal
'
)
{
return
false
}
// Next has pragma: __jsx.
return
callee
.
name
===
'
__jsx
'
&&
componentNode
.
value
===
'
link
'
}
packages/next/export/index.ts
浏览文件 @
fb81ecb2
...
...
@@ -388,6 +388,7 @@ export default async function exportApp(
subFolders
,
buildExport
:
options
.
buildExport
,
serverless
:
isTargetLikeServerless
(
nextConfig
.
target
),
optimizeFonts
:
nextConfig
.
experimental
.
optimizeFonts
,
})
for
(
const
validation
of
result
.
ampValidations
||
[])
{
...
...
packages/next/export/worker.ts
浏览文件 @
fb81ecb2
...
...
@@ -13,6 +13,8 @@ import 'next/dist/next-server/server/node-polyfill-fetch'
import
{
IncomingMessage
,
ServerResponse
}
from
'
http
'
import
{
ComponentType
}
from
'
react
'
import
{
GetStaticProps
}
from
'
../types
'
import
{
requireFontManifest
}
from
'
../next-server/server/require
'
import
{
FontManifest
}
from
'
../next-server/server/font-utils
'
const
envConfig
=
require
(
'
../next-server/lib/runtime-config
'
)
...
...
@@ -44,6 +46,7 @@ interface ExportPageInput {
serverRuntimeConfig
:
string
subFolders
:
string
serverless
:
boolean
optimizeFonts
:
boolean
}
interface
ExportPageResults
{
...
...
@@ -60,6 +63,8 @@ interface RenderOpts {
ampSkipValidation
?:
boolean
hybridAmp
?:
boolean
inAmpMode
?:
boolean
optimizeFonts
?:
boolean
fontManifest
?:
FontManifest
}
type
ComponentModule
=
ComponentType
<
{}
>
&
{
...
...
@@ -78,6 +83,7 @@ export default async function exportPage({
serverRuntimeConfig
,
subFolders
,
serverless
,
optimizeFonts
,
}:
ExportPageInput
):
Promise
<
ExportPageResults
>
{
let
results
:
ExportPageResults
=
{
ampValidations
:
[],
...
...
@@ -211,7 +217,14 @@ export default async function exportPage({
req
,
res
,
'
export
'
,
{
ampPath
},
{
ampPath
,
/// @ts-ignore
optimizeFonts
,
fontManifest
:
optimizeFonts
?
requireFontManifest
(
distDir
,
serverless
)
:
null
,
},
// @ts-ignore
params
)
...
...
@@ -246,7 +259,25 @@ export default async function exportPage({
html
=
components
.
Component
queryWithAutoExportWarn
()
}
else
{
curRenderOpts
=
{
...
components
,
...
renderOpts
,
ampPath
,
params
}
/**
* This sets environment variable to be used at the time of static export by head.tsx.
* Using this from process.env allows targetting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_FONTS`.
* TODO(prateekbh@): Remove this when experimental.optimizeFonts are being clened up.
*/
if
(
optimizeFonts
)
{
process
.
env
.
__NEXT_OPTIMIZE_FONTS
=
JSON
.
stringify
(
true
)
}
curRenderOpts
=
{
...
components
,
...
renderOpts
,
ampPath
,
params
,
optimizeFonts
,
fontManifest
:
optimizeFonts
?
requireFontManifest
(
distDir
,
serverless
)
:
null
,
}
// @ts-ignore
html
=
await
renderMethod
(
req
,
res
,
page
,
query
,
curRenderOpts
)
}
...
...
packages/next/next-server/lib/constants.ts
浏览文件 @
fb81ecb2
...
...
@@ -9,6 +9,7 @@ export const EXPORT_DETAIL = 'export-detail.json'
export
const
PRERENDER_MANIFEST
=
'
prerender-manifest.json
'
export
const
ROUTES_MANIFEST
=
'
routes-manifest.json
'
export
const
REACT_LOADABLE_MANIFEST
=
'
react-loadable-manifest.json
'
export
const
FONT_MANIFEST
=
'
font-manifest.json
'
export
const
SERVER_DIRECTORY
=
'
server
'
export
const
SERVERLESS_DIRECTORY
=
'
serverless
'
export
const
CONFIG_FILE
=
'
next.config.js
'
...
...
@@ -33,3 +34,4 @@ export const TEMPORARY_REDIRECT_STATUS = 307
export
const
PERMANENT_REDIRECT_STATUS
=
308
export
const
STATIC_PROPS_ID
=
'
__N_SSG
'
export
const
SERVER_PROPS_ID
=
'
__N_SSP
'
export
const
OPTIMIZED_FONT_PROVIDERS
=
[
'
https://fonts.googleapis.com/css
'
]
packages/next/next-server/lib/head.tsx
浏览文件 @
fb81ecb2
...
...
@@ -136,6 +136,21 @@ function reduceComponents(
.
reverse
()
.
map
((
c
:
React
.
ReactElement
<
any
>
,
i
:
number
)
=>
{
const
key
=
c
.
key
||
i
if
(
process
.
env
.
__NEXT_OPTIMIZE_FONTS
)
{
if
(
c
.
type
===
'
link
'
&&
c
.
props
[
'
href
'
]
&&
// TODO(prateekbh@): Replace this with const from `constants` when the tree shaking works.
[
'
https://fonts.googleapis.com/css
'
].
some
((
url
)
=>
c
.
props
[
'
href
'
].
startsWith
(
url
)
)
)
{
const
newProps
=
{
...(
c
.
props
||
{})
}
newProps
[
'
data-href
'
]
=
newProps
[
'
href
'
]
newProps
[
'
href
'
]
=
undefined
return
React
.
cloneElement
(
c
,
newProps
)
}
}
return
React
.
cloneElement
(
c
,
{
key
})
})
}
...
...
packages/next/next-server/lib/post-process.ts
0 → 100644
浏览文件 @
fb81ecb2
import
{
parse
,
HTMLElement
}
from
'
node-html-parser
'
import
{
OPTIMIZED_FONT_PROVIDERS
}
from
'
./constants
'
const
MIDDLEWARE_TIME_BUDGET
=
10
type
postProcessOptions
=
{
optimizeFonts
:
boolean
}
type
renderOptions
=
{
getFontDefinition
?:
(
url
:
string
)
=>
string
}
type
postProcessData
=
{
preloads
:
{
images
:
Array
<
string
>
}
}
interface
PostProcessMiddleware
{
inspect
:
(
originalDom
:
HTMLElement
,
data
:
postProcessData
,
options
:
renderOptions
)
=>
void
mutate
:
(
markup
:
string
,
data
:
postProcessData
,
options
:
renderOptions
)
=>
Promise
<
string
>
}
type
middlewareSignature
=
{
name
:
string
middleware
:
PostProcessMiddleware
condition
:
((
options
:
postProcessOptions
)
=>
boolean
)
|
null
}
const
middlewareRegistry
:
Array
<
middlewareSignature
>
=
[]
function
registerPostProcessor
(
name
:
string
,
middleware
:
PostProcessMiddleware
,
condition
?:
(
options
:
postProcessOptions
)
=>
boolean
)
{
middlewareRegistry
.
push
({
name
,
middleware
,
condition
:
condition
||
null
})
}
async
function
processHTML
(
html
:
string
,
data
:
renderOptions
,
options
:
postProcessOptions
):
Promise
<
string
>
{
// Don't parse unless there's at least one processor middleware
if
(
!
middlewareRegistry
[
0
])
{
return
html
}
const
postProcessData
:
postProcessData
=
{
preloads
:
{
images
:
[],
},
}
const
root
:
HTMLElement
=
parse
(
html
)
let
document
=
html
// Calls the middleware, with some instrumentation and logging
async
function
callMiddleWare
(
middleware
:
PostProcessMiddleware
,
name
:
string
)
{
let
timer
=
Date
.
now
()
middleware
.
inspect
(
root
,
postProcessData
,
data
)
const
inspectTime
=
Date
.
now
()
-
timer
document
=
await
middleware
.
mutate
(
document
,
postProcessData
,
data
)
timer
=
Date
.
now
()
-
timer
if
(
timer
>
MIDDLEWARE_TIME_BUDGET
)
{
console
.
warn
(
`The postprocess middleware "
${
name
}
" took
${
timer
}
ms(
${
inspectTime
}
,
${
timer
-
inspectTime
}
) to complete. This is longer than the
${
MIDDLEWARE_TIME_BUDGET
}
limit.`
)
}
return
}
for
(
let
i
=
0
;
i
<
middlewareRegistry
.
length
;
i
++
)
{
let
middleware
=
middlewareRegistry
[
i
]
if
(
!
middleware
.
condition
||
middleware
.
condition
(
options
))
{
await
callMiddleWare
(
middlewareRegistry
[
i
].
middleware
,
middlewareRegistry
[
i
].
name
)
}
}
return
document
}
class
FontOptimizerMiddleware
implements
PostProcessMiddleware
{
fontDefinitions
:
Array
<
string
>
=
[]
inspect
(
originalDom
:
HTMLElement
,
_data
:
postProcessData
,
options
:
renderOptions
)
{
if
(
!
options
.
getFontDefinition
)
{
return
}
// collecting all the requested font definitions
originalDom
.
querySelectorAll
(
'
link
'
)
.
filter
(
(
tag
:
HTMLElement
)
=>
tag
.
getAttribute
(
'
rel
'
)
===
'
stylesheet
'
&&
tag
.
hasAttribute
(
'
data-href
'
)
&&
OPTIMIZED_FONT_PROVIDERS
.
some
((
url
)
=>
tag
.
getAttribute
(
'
data-href
'
).
startsWith
(
url
)
)
)
.
forEach
((
element
:
HTMLElement
)
=>
{
const
url
=
element
.
getAttribute
(
'
data-href
'
)
this
.
fontDefinitions
.
push
(
url
)
})
}
mutate
=
async
(
markup
:
string
,
_data
:
postProcessData
,
options
:
renderOptions
)
=>
{
let
result
=
markup
if
(
!
options
.
getFontDefinition
)
{
return
markup
}
for
(
const
key
in
this
.
fontDefinitions
)
{
const
url
=
this
.
fontDefinitions
[
key
]
if
(
result
.
indexOf
(
`<style data-href="
${
url
}
">`
)
>
-
1
)
{
// The font is already optimized and probably the response is cached
continue
}
const
fontContent
=
options
.
getFontDefinition
(
url
)
result
=
result
.
replace
(
'
</head>
'
,
`<style data-href="
${
url
}
">
${
fontContent
.
replace
(
/
(\n
|
\s)
/g
,
''
)}
</style></head>`
)
}
return
result
}
}
// Initialization
registerPostProcessor
(
'
Inline-Fonts
'
,
new
FontOptimizerMiddleware
(),
// Using process.env because passing Experimental flag through loader is not possible.
// @ts-ignore
(
options
)
=>
options
.
optimizeFonts
||
process
.
env
.
__NEXT_OPTIMIZE_FONTS
)
export
default
processHTML
packages/next/next-server/server/config.ts
浏览文件 @
fb81ecb2
...
...
@@ -52,6 +52,7 @@ const defaultConfig: { [key: string]: any } = {
workerThreads
:
false
,
pageEnv
:
false
,
productionBrowserSourceMaps
:
false
,
optimizeFonts
:
false
,
scrollRestoration
:
false
,
},
future
:
{
...
...
packages/next/next-server/server/font-utils.ts
0 → 100644
浏览文件 @
fb81ecb2
const
https
=
require
(
'
https
'
)
const
CHROME_UA
=
'
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
'
const
IE_UA
=
'
Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko
'
export
type
FontManifest
=
Array
<
{
url
:
string
content
:
string
}
>
function
getFontForUA
(
url
:
string
,
UA
:
string
):
Promise
<
String
>
{
return
new
Promise
((
resolve
)
=>
{
let
rawData
:
any
=
''
https
.
get
(
url
,
{
headers
:
{
'
user-agent
'
:
UA
,
},
},
(
res
:
any
)
=>
{
res
.
on
(
'
data
'
,
(
chunk
:
any
)
=>
{
rawData
+=
chunk
})
res
.
on
(
'
end
'
,
()
=>
{
resolve
(
rawData
.
toString
(
'
utf8
'
))
})
}
)
})
}
export
async
function
getFontDefinitionFromNetwork
(
url
:
string
):
Promise
<
string
>
{
let
result
=
''
/**
* The order of IE -> Chrome is important, other wise chrome starts loading woff1.
* CSS cascading 🤷♂️.
*/
result
+=
await
getFontForUA
(
url
,
IE_UA
)
result
+=
await
getFontForUA
(
url
,
CHROME_UA
)
return
result
}
export
function
getFontDefinitionFromManifest
(
url
:
string
,
manifest
:
FontManifest
):
string
{
return
(
manifest
.
find
((
font
)
=>
{
if
(
font
&&
font
.
url
===
url
)
{
return
true
}
return
false
})?.
content
||
''
)
}
packages/next/next-server/server/next-server.ts
浏览文件 @
fb81ecb2
...
...
@@ -43,7 +43,7 @@ import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
import
{
loadComponents
,
LoadComponentsReturnType
}
from
'
./load-components
'
import
{
normalizePagePath
}
from
'
./normalize-page-path
'
import
{
RenderOpts
,
RenderOptsPartial
,
renderToHTML
}
from
'
./render
'
import
{
getPagePath
}
from
'
./require
'
import
{
getPagePath
,
requireFontManifest
}
from
'
./require
'
import
Router
,
{
DynamicRoutes
,
PageChecker
,
...
...
@@ -63,6 +63,7 @@ import './node-polyfill-fetch'
import
{
PagesManifest
}
from
'
../../build/webpack/plugins/pages-manifest-plugin
'
import
{
removePathTrailingSlash
}
from
'
../../client/normalize-trailing-slash
'
import
getRouteFromAssetPath
from
'
../lib/router/utils/get-route-from-asset-path
'
import
{
FontManifest
}
from
'
./font-utils
'
const
getCustomRouteMatcher
=
pathMatch
(
true
)
...
...
@@ -119,6 +120,8 @@ export default class Server {
customServer
?:
boolean
ampOptimizerConfig
?:
{
[
key
:
string
]:
any
}
basePath
:
string
optimizeFonts
:
boolean
fontManifest
:
FontManifest
}
private
compression
?:
Middleware
private
onErrorMiddleware
?:
({
err
}:
{
err
:
Error
})
=>
Promise
<
void
>
...
...
@@ -165,6 +168,10 @@ export default class Server {
customServer
:
customServer
===
true
?
true
:
undefined
,
ampOptimizerConfig
:
this
.
nextConfig
.
experimental
.
amp
?.
optimizer
,
basePath
:
this
.
nextConfig
.
basePath
,
optimizeFonts
:
this
.
nextConfig
.
experimental
.
optimizeFonts
,
fontManifest
:
this
.
nextConfig
.
experimental
.
optimizeFonts
?
requireFontManifest
(
this
.
distDir
,
this
.
_isLikeServerless
)
:
null
,
}
// Only the `publicRuntimeConfig` key is exposed to the client side
...
...
@@ -219,6 +226,16 @@ export default class Server {
),
flushToDisk
:
this
.
nextConfig
.
experimental
.
sprFlushToDisk
,
})
/**
* This sets environment variable to be used at the time of SSR by head.tsx.
* Using this from process.env allows targetting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_FONTS`.
* TODO(prateekbh@): Remove this when experimental.optimizeFonts are being clened up.
*/
if
(
this
.
renderOpts
.
optimizeFonts
)
{
process
.
env
.
__NEXT_OPTIMIZE_FONTS
=
JSON
.
stringify
(
true
)
}
}
protected
currentPhase
():
string
{
...
...
@@ -1044,7 +1061,10 @@ export default class Server {
renderResult
=
await
(
components
.
Component
as
any
).
renderReqToHTML
(
req
,
res
,
'
passthrough
'
'
passthrough
'
,
{
fontManifest
:
this
.
renderOpts
.
fontManifest
,
}
)
html
=
renderResult
.
html
...
...
packages/next/next-server/server/render.tsx
浏览文件 @
fb81ecb2
...
...
@@ -45,6 +45,8 @@ import { tryGetPreviewData, __ApiPreviewProps } from './api-utils'
import
{
getPageFiles
}
from
'
./get-page-files
'
import
{
LoadComponentsReturnType
,
ManifestItem
}
from
'
./load-components
'
import
optimizeAmp
from
'
./optimize-amp
'
import
postProcess
from
'
../lib/post-process
'
import
{
FontManifest
,
getFontDefinitionFromManifest
}
from
'
./font-utils
'
function
noRouter
()
{
const
message
=
...
...
@@ -143,6 +145,8 @@ export type RenderOptsPartial = {
previewProps
:
__ApiPreviewProps
basePath
:
string
unstable_runtimeJS
?:
false
optimizeFonts
:
boolean
fontManifest
?:
FontManifest
}
export
type
RenderOpts
=
LoadComponentsReturnType
&
RenderOptsPartial
...
...
@@ -269,6 +273,7 @@ export async function renderToHTML(
pageConfig
=
{},
Component
,
buildManifest
,
fontManifest
,
reactLoadableManifest
,
ErrorDebug
,
getStaticProps
,
...
...
@@ -280,6 +285,13 @@ export async function renderToHTML(
basePath
,
}
=
renderOpts
const
getFontDefinition
=
(
url
:
string
):
string
=>
{
if
(
fontManifest
)
{
return
getFontDefinitionFromManifest
(
url
,
fontManifest
)
}
return
''
}
const
callMiddleware
=
async
(
method
:
string
,
args
:
any
[],
props
=
false
)
=>
{
let
results
:
any
=
props
?
{}
:
[]
...
...
@@ -781,6 +793,16 @@ export async function renderToHTML(
}
}
html
=
await
postProcess
(
html
,
{
getFontDefinition
,
},
{
optimizeFonts
:
renderOpts
.
optimizeFonts
,
}
)
if
(
inAmpMode
||
hybridAmp
)
{
// fix & being escaped for amphtml rel link
html
=
html
.
replace
(
/&amp=1/g
,
'
&=1
'
)
...
...
packages/next/next-server/server/require.ts
浏览文件 @
fb81ecb2
...
...
@@ -4,6 +4,7 @@ import {
PAGES_MANIFEST
,
SERVER_DIRECTORY
,
SERVERLESS_DIRECTORY
,
FONT_MANIFEST
,
}
from
'
../lib/constants
'
import
{
normalizePagePath
,
denormalizePagePath
}
from
'
./normalize-page-path
'
import
{
PagesManifest
}
from
'
../../build/webpack/plugins/pages-manifest-plugin
'
...
...
@@ -54,3 +55,12 @@ export function requirePage(
}
return
require
(
pagePath
)
}
export
function
requireFontManifest
(
distDir
:
string
,
serverless
:
boolean
)
{
const
serverBuildPath
=
join
(
distDir
,
serverless
?
SERVERLESS_DIRECTORY
:
SERVER_DIRECTORY
)
const
fontManifest
=
require
(
join
(
serverBuildPath
,
FONT_MANIFEST
))
return
fontManifest
}
packages/next/package.json
浏览文件 @
fb81ecb2
...
...
@@ -78,6 +78,7 @@
"@babel/types"
:
"7.9.6"
,
"@next/react-dev-overlay"
:
"9.5.0"
,
"@next/react-refresh-utils"
:
"9.5.0"
,
"ast-types"
:
"0.13.2"
,
"babel-plugin-syntax-jsx"
:
"6.18.0"
,
"babel-plugin-transform-define"
:
"2.0.0"
,
"babel-plugin-transform-react-remove-prop-types"
:
"0.4.24"
,
...
...
@@ -94,6 +95,7 @@
"mkdirp"
:
"0.5.3"
,
"native-url"
:
"0.3.4"
,
"neo-async"
:
"2.6.1"
,
"node-html-parser"
:
"^1.2.19"
,
"pnp-webpack-plugin"
:
"1.6.4"
,
"postcss"
:
"7.0.32"
,
"process"
:
"0.11.10"
,
...
...
@@ -155,7 +157,6 @@
"@zeit/ncc"
:
"0.22.0"
,
"amphtml-validator"
:
"1.0.31"
,
"arg"
:
"4.1.0"
,
"ast-types"
:
"0.13.2"
,
"async-retry"
:
"1.2.3"
,
"async-sema"
:
"3.0.0"
,
"babel-loader"
:
"8.1.0"
,
...
...
packages/next/pages/_document.tsx
浏览文件 @
fb81ecb2
import
PropTypes
from
'
prop-types
'
import
React
,
{
useContext
,
Component
}
from
'
react
'
import
React
,
{
useContext
,
Component
,
ReactNode
}
from
'
react
'
import
flush
from
'
styled-jsx/server
'
import
{
AMP_RENDER_TARGET
}
from
'
../next-server/lib/constants
'
import
{
AMP_RENDER_TARGET
,
OPTIMIZED_FONT_PROVIDERS
,
}
from
'
../next-server/lib/constants
'
import
{
DocumentContext
as
DocumentComponentContext
}
from
'
../next-server/lib/document-context
'
import
{
DocumentContext
,
...
...
@@ -236,6 +239,24 @@ export class Head extends Component<
))
}
makeStylesheetInert
(
node
:
ReactNode
):
ReactNode
{
return
React
.
Children
.
map
(
node
,
(
c
:
any
)
=>
{
if
(
c
.
type
===
'
link
'
&&
c
.
props
[
'
href
'
]
&&
OPTIMIZED_FONT_PROVIDERS
.
some
((
url
)
=>
c
.
props
[
'
href
'
].
startsWith
(
url
))
)
{
const
newProps
=
{
...(
c
.
props
||
{})
}
newProps
[
'
data-href
'
]
=
newProps
[
'
href
'
]
newProps
[
'
href
'
]
=
undefined
return
React
.
cloneElement
(
c
,
newProps
)
}
else
if
(
c
.
props
&&
c
.
props
[
'
children
'
])
{
c
.
props
[
'
children
'
]
=
this
.
makeStylesheetInert
(
c
.
props
[
'
children
'
])
}
return
c
})
}
render
()
{
const
{
styles
,
...
...
@@ -278,6 +299,10 @@ export class Head extends Component<
)
}
if
(
process
.
env
.
__NEXT_OPTIMIZE_FONTS
)
{
children
=
this
.
makeStylesheetInert
(
children
)
}
let
hasAmphtmlRel
=
false
let
hasCanonicalRel
=
false
...
...
@@ -285,7 +310,6 @@ export class Head extends Component<
head
=
React
.
Children
.
map
(
head
||
[],
(
child
)
=>
{
if
(
!
child
)
return
child
const
{
type
,
props
}
=
child
if
(
inAmpMode
)
{
let
badProp
:
string
=
''
...
...
@@ -435,7 +459,9 @@ export class Head extends Component<
href
=
{
canonicalBase
+
getAmpPath
(
ampPath
,
dangerousAsPath
)
}
/>
)
}
{
this
.
getCssLinks
()
}
{
process
.
env
.
__NEXT_OPTIMIZE_FONTS
?
this
.
makeStylesheetInert
(
this
.
getCssLinks
())
:
this
.
getCssLinks
()
}
{
!
disableRuntimeJS
&&
this
.
getPreloadDynamicChunks
()
}
{
!
disableRuntimeJS
&&
this
.
getPreloadMainLinks
()
}
{
this
.
context
.
_documentProps
.
isDevelopment
&&
(
...
...
test/integration/build-output/test/index.test.js
浏览文件 @
fb81ecb2
...
...
@@ -98,7 +98,7 @@ describe('Build Output', () => {
expect
(
parseFloat
(
indexFirstLoad
)
-
60
).
toBeLessThanOrEqual
(
0
)
expect
(
indexFirstLoad
.
endsWith
(
'
kB
'
)).
toBe
(
true
)
expect
(
parseFloat
(
err404Size
)
-
3.
4
).
toBeLessThanOrEqual
(
0
)
expect
(
parseFloat
(
err404Size
)
-
3.
6
).
toBeLessThanOrEqual
(
0
)
expect
(
err404Size
.
endsWith
(
'
kB
'
)).
toBe
(
true
)
expect
(
parseFloat
(
err404FirstLoad
)
-
63
).
toBeLessThanOrEqual
(
0
)
...
...
test/integration/font-optimization/pages/_document.js
0 → 100644
浏览文件 @
fb81ecb2
import
*
as
React
from
'
react
'
/// @ts-ignore
import
Document
,
{
Main
,
NextScript
,
Head
}
from
'
next/document
'
export
default
class
MyDocument
extends
Document
{
constructor
(
props
)
{
super
(
props
)
const
{
__NEXT_DATA__
,
ids
}
=
props
if
(
ids
)
{
__NEXT_DATA__
.
ids
=
ids
}
}
render
()
{
return
(
<
html
>
<
Head
>
<
link
rel
=
"
stylesheet
"
href
=
"
https://fonts.googleapis.com/css?family=Voces
"
/>
<
/Head
>
<
body
>
<
Main
/>
<
NextScript
/>
<
/body
>
<
/html
>
)
}
}
test/integration/font-optimization/pages/index.js
0 → 100644
浏览文件 @
fb81ecb2
import
React
from
'
react
'
const
Page
=
()
=>
{
return
<
div
>
Hi
!<
/div
>
}
export
default
Page
test/integration/font-optimization/pages/stars.js
0 → 100644
浏览文件 @
fb81ecb2
import
Head
from
'
next/head
'
function
Home
({
stars
})
{
return
(
<
div
className
=
"
container
"
>
<
Head
>
<
title
>
Create
Next
App
<
/title
>
<
link
rel
=
"
icon
"
href
=
"
/favicon.ico
"
/>
<
link
rel
=
"
stylesheet
"
href
=
"
https://fonts.googleapis.com/css2?family=Roboto:wght@700
"
><
/link
>
<
/Head
>
<
main
>
<
div
>
Next
stars
:
{
stars
}
<
/div
>
<
/main
>
<
/div
>
)
}
Home
.
getInitialProps
=
async
()
=>
{
return
{
stars
:
Math
.
random
()
*
1000
}
}
export
default
Home
test/integration/font-optimization/pages/static-head.js
0 → 100644
浏览文件 @
fb81ecb2
import
React
from
'
react
'
import
Head
from
'
next/head
'
const
Page
=
()
=>
{
return
(
<>
<
Head
>
<
link
href
=
"
https://fonts.googleapis.com/css2?family=Modak
"
rel
=
"
stylesheet
"
/>
<
/Head
>
<
div
>
Hi
!<
/div
>
<
/
>
)
}
export
default
Page
test/integration/font-optimization/server.js
0 → 100644
浏览文件 @
fb81ecb2
const
http
=
require
(
'
http
'
)
const
url
=
require
(
'
url
'
)
const
fs
=
require
(
'
fs
'
)
const
path
=
require
(
'
path
'
)
const
server
=
http
.
createServer
(
async
(
req
,
res
)
=>
{
let
{
pathname
}
=
url
.
parse
(
req
.
url
)
pathname
=
pathname
.
replace
(
/
\/
$/
,
''
)
let
isDataReq
=
false
if
(
pathname
.
startsWith
(
'
/_next/data
'
))
{
isDataReq
=
true
pathname
=
pathname
.
replace
(
`/_next/data/
${
process
.
env
.
BUILD_ID
}
/`
,
'
/
'
)
.
replace
(
/
\.
json$/
,
''
)
}
console
.
log
(
'
serving
'
,
pathname
)
if
(
pathname
===
'
/favicon.ico
'
)
{
res
.
statusCode
=
404
return
res
.
end
()
}
if
(
pathname
.
startsWith
(
'
/_next/static/
'
))
{
res
.
write
(
fs
.
readFileSync
(
path
.
join
(
__dirname
,
'
./.next/static/
'
,
decodeURI
(
pathname
.
slice
(
'
/_next/static/
'
.
length
))
),
'
utf8
'
)
)
return
res
.
end
()
}
else
{
const
ext
=
isDataReq
?
'
json
'
:
'
html
'
if
(
fs
.
existsSync
(
path
.
join
(
__dirname
,
`./.next/serverless/pages
${
pathname
}
.
${
ext
}
`
)
)
)
{
res
.
write
(
fs
.
readFileSync
(
path
.
join
(
__dirname
,
`./.next/serverless/pages
${
pathname
}
.
${
ext
}
`
),
'
utf8
'
)
)
return
res
.
end
()
}
let
re
try
{
re
=
require
(
`./.next/serverless/pages
${
pathname
}
`
)
}
catch
{
const
d
=
decodeURI
(
pathname
)
if
(
fs
.
existsSync
(
path
.
join
(
__dirname
,
`./.next/serverless/pages
${
d
}
.
${
ext
}
`
)
)
)
{
res
.
write
(
fs
.
readFileSync
(
path
.
join
(
__dirname
,
`./.next/serverless/pages
${
d
}
.
${
ext
}
`
),
'
utf8
'
)
)
return
res
.
end
()
}
const
routesManifest
=
require
(
'
./.next/routes-manifest.json
'
)
const
{
dynamicRoutes
}
=
routesManifest
dynamicRoutes
.
some
(({
page
,
regex
})
=>
{
if
(
new
RegExp
(
regex
).
test
(
pathname
))
{
if
(
fs
.
existsSync
(
path
.
join
(
__dirname
,
`./.next/serverless/pages
${
page
}
.
${
ext
}
`
)
)
)
{
res
.
write
(
fs
.
readFileSync
(
path
.
join
(
__dirname
,
`./.next/serverless/pages
${
page
}
.
${
ext
}
`
),
'
utf8
'
)
)
res
.
end
()
return
true
}
re
=
require
(
`./.next/serverless/pages
${
page
}
`
)
return
true
}
return
false
})
}
if
(
!
res
.
finished
)
{
try
{
return
await
(
typeof
re
.
render
===
'
function
'
?
re
.
render
(
req
,
res
)
:
re
.
default
(
req
,
res
))
}
catch
(
e
)
{
console
.
log
(
'
FAIL_FUNCTION
'
,
e
)
res
.
statusCode
=
500
res
.
write
(
'
FAIL_FUNCTION
'
)
res
.
end
()
}
}
}
})
server
.
listen
(
process
.
env
.
PORT
,
()
=>
{
console
.
log
(
'
ready on
'
,
process
.
env
.
PORT
)
})
test/integration/font-optimization/test/index.test.js
0 → 100644
浏览文件 @
fb81ecb2
/* eslint-env jest */
import
{
join
}
from
'
path
'
import
{
killApp
,
findPort
,
nextStart
,
nextBuild
,
renderViaHTTP
,
initNextServerScript
,
}
from
'
next-test-utils
'
import
fs
from
'
fs-extra
'
jest
.
setTimeout
(
1000
*
30
)
const
appDir
=
join
(
__dirname
,
'
../
'
)
const
nextConfig
=
join
(
appDir
,
'
next.config.js
'
)
let
builtServerPagesDir
let
builtPage
let
appPort
let
app
const
fsExists
=
(
file
)
=>
fs
.
access
(
file
)
.
then
(()
=>
true
)
.
catch
(()
=>
false
)
async
function
getBuildId
()
{
return
fs
.
readFile
(
join
(
appDir
,
'
.next
'
,
'
BUILD_ID
'
),
'
utf8
'
)
}
const
startServerlessEmulator
=
async
(
dir
,
port
)
=>
{
const
scriptPath
=
join
(
dir
,
'
server.js
'
)
const
env
=
Object
.
assign
(
{},
{
...
process
.
env
},
{
PORT
:
port
,
BUILD_ID
:
await
getBuildId
()
}
)
return
initNextServerScript
(
scriptPath
,
/ready on/i
,
env
)
}
function
runTests
()
{
it
(
'
should inline the google fonts for static pages
'
,
async
()
=>
{
const
html
=
await
renderViaHTTP
(
appPort
,
'
/index
'
)
expect
(
await
fsExists
(
builtPage
(
'
font-manifest.json
'
))).
toBe
(
true
)
expect
(
html
).
toContain
(
'
<link rel="stylesheet" data-href="https://fonts.googleapis.com/css?family=Voces"/>
'
)
expect
(
html
).
toMatch
(
/<style data-href="https:
\/\/
fonts
\.
googleapis
\.
com
\/
css
\?
family=Voces">.*<
\/
style>/
)
})
it
(
'
should inline the google fonts for static pages with Next/Head
'
,
async
()
=>
{
const
html
=
await
renderViaHTTP
(
appPort
,
'
/static-head
'
)
expect
(
await
fsExists
(
builtPage
(
'
font-manifest.json
'
))).
toBe
(
true
)
expect
(
html
).
toContain
(
'
<link rel="stylesheet" data-href="https://fonts.googleapis.com/css2?family=Modak"/>
'
)
expect
(
html
).
toMatch
(
/<style data-href="https:
\/\/
fonts
\.
googleapis
\.
com
\/
css2
\?
family=Modak">.*<
\/
style>/
)
})
it
(
'
should inline the google fonts for SSR pages
'
,
async
()
=>
{
const
html
=
await
renderViaHTTP
(
appPort
,
'
/stars
'
)
expect
(
await
fsExists
(
builtPage
(
'
font-manifest.json
'
))).
toBe
(
true
)
expect
(
html
).
toContain
(
'
<link rel="stylesheet" data-href="https://fonts.googleapis.com/css2?family=Roboto:wght@700"/>
'
)
expect
(
html
).
toMatch
(
/<style data-href="https:
\/\/
fonts
\.
googleapis
\.
com
\/
css2
\?
family=Roboto:wght@700">.*<
\/
style>/
)
})
}
describe
(
'
Font optimization for SSR apps
'
,
()
=>
{
beforeAll
(
async
()
=>
{
await
fs
.
writeFile
(
nextConfig
,
`module.exports = { experimental: {optimizeFonts: true} }`
,
'
utf8
'
)
await
nextBuild
(
appDir
)
appPort
=
await
findPort
()
app
=
await
nextStart
(
appDir
,
appPort
)
builtServerPagesDir
=
join
(
appDir
,
'
.next
'
,
'
server
'
)
builtPage
=
(
file
)
=>
join
(
builtServerPagesDir
,
file
)
})
afterAll
(()
=>
killApp
(
app
))
runTests
()
})
describe
(
'
Font optimization for serverless apps
'
,
()
=>
{
beforeAll
(
async
()
=>
{
await
fs
.
writeFile
(
nextConfig
,
`module.exports = { target: 'serverless', experimental: {optimizeFonts: true} }`
,
'
utf8
'
)
await
nextBuild
(
appDir
)
appPort
=
await
findPort
()
app
=
await
nextStart
(
appDir
,
appPort
)
builtServerPagesDir
=
join
(
appDir
,
'
.next
'
,
'
serverless
'
)
builtPage
=
(
file
)
=>
join
(
builtServerPagesDir
,
file
)
})
afterAll
(()
=>
killApp
(
app
))
runTests
()
})
describe
(
'
Font optimization for emulated serverless apps
'
,
()
=>
{
beforeAll
(
async
()
=>
{
await
fs
.
writeFile
(
nextConfig
,
`module.exports = { target: 'experimental-serverless-trace', experimental: {optimizeFonts: true} }`
,
'
utf8
'
)
await
nextBuild
(
appDir
)
appPort
=
await
findPort
()
await
startServerlessEmulator
(
appDir
,
appPort
)
builtServerPagesDir
=
join
(
appDir
,
'
.next
'
,
'
serverless
'
)
builtPage
=
(
file
)
=>
join
(
builtServerPagesDir
,
file
)
})
afterAll
(
async
()
=>
{
await
fs
.
remove
(
nextConfig
)
})
runTests
()
})
yarn.lock
浏览文件 @
fb81ecb2
...
...
@@ -7797,6 +7797,11 @@ hawk@~6.0.2:
hoek "4.x.x"
sntp "2.x.x"
he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
header-case@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d"
...
...
@@ -10826,6 +10831,13 @@ node-gyp@^4.0.0:
tar "^4.4.8"
which "1"
node-html-parser@^1.2.19:
version "1.2.19"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.2.19.tgz#2cb14ce7981dfe2c0f5af53cf8654a3d49cded7d"
integrity sha512-MQvBz+qk7SbqNPp0c7hR0F8lRTPXK5n2tww4eFmXf+cXp5hZHtL5rJHlAWlcjzRep+T5Pd5lz3lqFgN7IFYEiw==
dependencies:
he "1.1.1"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录