Skip to content

Commit

Permalink
wip: basic SSR support
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 18, 2021
1 parent 10c1e15 commit 7a15ada
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 58 deletions.
49 changes: 47 additions & 2 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Rollup, {
RollupWarning,
WarningHandler,
OutputOptions,
RollupOutput
RollupOutput,
ExternalOption
} from 'rollup'
import { buildReporterPlugin } from './plugins/reporter'
import { buildDefinePlugin } from './plugins/define'
Expand Down Expand Up @@ -282,14 +283,27 @@ async function doBuild(
const outDir = resolve(options.outDir)
const publicDir = resolve('public')

// inject ssr arg to plugin load/transform hooks
const plugins = (options.ssr
? config.plugins.map((p) => injectSsrFlagToHooks(p))
: config.plugins) as Plugin[]

// inject ssrExternal if present
const userExternal = options.rollupOptions?.external
const external =
options.ssr && config.ssrExternal
? resolveExternal(config.ssrExternal, userExternal)
: userExternal

const rollup = require('rollup') as typeof Rollup

try {
const bundle = await rollup.rollup({
input,
preserveEntrySignatures: libOptions ? 'strict' : false,
...options.rollupOptions,
plugins: config.plugins as Plugin[],
plugins,
external,
onwarn(warning, warn) {
onRollupWarning(warning, warn, config)
}
Expand Down Expand Up @@ -459,3 +473,34 @@ export function onRollupWarning(
}
}
}

export function resolveExternal(
existing: string[],
user: ExternalOption | undefined
): ExternalOption {
if (!user) return existing
if (typeof user !== 'function') {
return existing.concat(user as any[])
}
return ((id, parentId, isResolved) => {
if (existing.includes(id)) return true
return user(id, parentId, isResolved)
}) as ExternalOption
}

function injectSsrFlagToHooks(p: Plugin): Plugin {
const { resolveId, load, transform } = p
return {
...p,
resolveId: wrapSsrHook(resolveId),
load: wrapSsrHook(load),
transform: wrapSsrHook(transform)
}
}

function wrapSsrHook(fn: Function | undefined) {
if (!fn) return
return function (this: any, ...args: any[]) {
return fn.call(this, ...args, true)
}
}
5 changes: 5 additions & 0 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ export interface UserConfig {
* Default: true
*/
clearScreen?: boolean
/**
* Externalize deps for SSR. These deps must provide a CommonJS build that
* can be `required()` and has the same module signature as its ESM build.
*/
ssrExternal?: string[]
}

export interface InlineConfig extends UserConfig {
Expand Down
18 changes: 2 additions & 16 deletions packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import fs from 'fs'
import path from 'path'
import chalk from 'chalk'
import Rollup, { ExternalOption } from 'rollup'
import Rollup from 'rollup'
import { createHash } from 'crypto'
import { ResolvedConfig, sortUserPlugins } from '../config'
import { SUPPORTED_EXTS } from '../constants'
import { init, parse } from 'es-module-lexer'
import { onRollupWarning } from '../build'
import { onRollupWarning, resolveExternal } from '../build'
import {
createDebugger,
emptyDir,
Expand Down Expand Up @@ -444,20 +444,6 @@ async function resolveLinkedDeps(
})
}

function resolveExternal(
existing: string[],
user: ExternalOption | undefined
): ExternalOption {
if (!user) return existing
if (typeof user !== 'function') {
return existing.concat(user as any[])
}
return ((id, parentId, isResolved) => {
if (existing.includes(id)) return true
return user(id, parentId, isResolved)
}) as ExternalOption
}

const lockfileFormats = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']

let cachedHash: string | undefined
Expand Down
16 changes: 10 additions & 6 deletions packages/vite/src/node/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { UserConfig } from './config'
import {
LoadResult,
Plugin as RollupPlugin,
PluginContext,
TransformPluginContext,
TransformResult
} from 'rollup'
Expand Down Expand Up @@ -109,14 +110,17 @@ export interface Plugin extends RollupPlugin {
): Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>

/**
* SSR-specific load/transform hooks called during SSR module loads. If these
* are not provided, then the normal load/transform hooks will be called if
* present.
* extend hooks with ssr flag
*/
ssrLoad?(id: string): Promise<LoadResult> | LoadResult
ssrTransform?(
load?(
this: PluginContext,
id: string,
ssr?: boolean
): Promise<LoadResult> | LoadResult
transform?(
this: TransformPluginContext,
code: string,
id: string
id: string,
ssr?: boolean
): Promise<TransformResult> | TransformResult
}
14 changes: 9 additions & 5 deletions packages/vite/src/node/plugins/importAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
server = _server
},

async transform(source, importer) {
async transform(source, importer, ssr) {
const prettyImporter = prettifyUrl(importer, config.root)

if (canSkip(importer)) {
Expand Down Expand Up @@ -237,10 +237,6 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
const rawUrl = source.slice(start, end)
let url = rawUrl

if (isExternalUrl(url) || isDataUrl(url)) {
continue
}

// check import.meta usage
if (url === 'import.meta') {
const prop = source.slice(end, end + 4)
Expand Down Expand Up @@ -293,6 +289,14 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {

// If resolvable, let's resolve it
if (dynamicIndex === -1 || isLiteralDynamicId) {
// skip external / data uri
if (isExternalUrl(url) || isDataUrl(url)) {
continue
}
// skip ssr external
if (ssr && config.ssrExternal?.includes(url)) {
continue
}
// skip client
if (url === CLIENT_PUBLIC_PATH) {
continue
Expand Down
4 changes: 0 additions & 4 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@ export function resolvePlugin(
server = _server
},

configResolved(_config) {
config = _config
},

resolveId(id, importer) {
if (id === browserExternalId) {
return id
Expand Down
30 changes: 17 additions & 13 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,31 +366,35 @@ export async function createServer(
// error handler
app.use(errorMiddleware(server, middlewareMode))

const runOptimize = async () => {
if (config.optimizeCacheDir) {
// run optimizer
await optimizeDeps(config)
// after optimization, read updated optimization metadata
const dataPath = path.resolve(config.optimizeCacheDir, 'metadata.json')
if (fs.existsSync(dataPath)) {
server.optimizeDepsMetadata = JSON.parse(
fs.readFileSync(dataPath, 'utf-8')
)
}
}
}

if (httpServer) {
// overwrite listen to run optimizer before server start
const listen = httpServer.listen.bind(httpServer)
httpServer.listen = (async (port: number, ...args: any[]) => {
await container.buildStart({})

if (config.optimizeCacheDir) {
// run optimizer
await optimizeDeps(config)
// after optimization, read updated optimization metadata
const dataPath = path.resolve(config.optimizeCacheDir, 'metadata.json')
if (fs.existsSync(dataPath)) {
server.optimizeDepsMetadata = JSON.parse(
fs.readFileSync(dataPath, 'utf-8')
)
}
}

await runOptimize()
return listen(port, ...args)
}) as any

httpServer.once('listening', () => {
// update actual port since this may be different from initial value
serverConfig.port = (httpServer.address() as AddressInfo).port
})
} else {
await runOptimize()
}

return server
Expand Down
10 changes: 4 additions & 6 deletions packages/vite/src/node/server/pluginContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,9 @@ export async function createPluginContainer(
async load(id, ssr = false) {
const ctx = new Context()
for (const plugin of plugins) {
const load = (ssr && plugin.ssrLoad) || plugin.load
if (!load) continue
if (!plugin.load) continue
ctx._activePlugin = plugin
const result = await load.call(ctx as any, id)
const result = await plugin.load.call(ctx as any, id, ssr)
if (result != null) {
return result
}
Expand All @@ -496,15 +495,14 @@ export async function createPluginContainer(
async transform(code, id, inMap, ssr = false) {
const ctx = new TransformContext(id, code, inMap as SourceMap)
for (const plugin of plugins) {
const transform = (ssr && plugin.ssrTransform) || plugin.transform
if (!transform) continue
if (!plugin.transform) continue
ctx._activePlugin = plugin
ctx._activeId = id
ctx._activeCode = code
const start = Date.now()
let result
try {
result = await transform.call(ctx as any, code, id)
result = await plugin.transform.call(ctx as any, code, id, ssr)
} catch (e) {
ctx.error(e)
}
Expand Down
36 changes: 30 additions & 6 deletions packages/vite/src/node/server/ssrModuleLoader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import path from 'path'
import { ViteDevServer } from '..'
import { resolveFrom } from '../utils'
import { transformRequest } from './transformRequest'

export async function ssrLoadModule(
Expand All @@ -17,17 +19,39 @@ export async function ssrLoadModule(
throw new Error(`failed to load module for ssr: $${url}`)
}

await Promise.all(result.deps!.map((dep) => ssrLoadModule(dep, server)))
const external = server.config.ssrExternal

await Promise.all(
result.deps!.map((dep) => {
if (!external?.includes(dep)) {
return ssrLoadModule(dep, server)
}
})
)

const __import__ = (dep: string) => {
return moduleGraph.urlToModuleMap.get(dep)?.ssrModule
if (external?.includes(dep)) {
if (mod.file) {
return require(resolveFrom(dep, path.dirname(mod.file)))
} else {
return require(dep)
}
} else {
return moduleGraph.urlToModuleMap.get(dep)?.ssrModule
}
}
const __exports__ = {}

new Function(`__import__`, `__exports__`, result.code)(
__import__,
__exports__
)
try {
new Function(`__import__`, `__exports__`, result.code)(
__import__,
__exports__
)
} catch (e) {
// console.log(e.message)
// console.log(result.code)
// TODO
}

mod.ssrModule = __exports__
return __exports__
Expand Down
11 changes: 11 additions & 0 deletions packages/vite/src/node/server/ssrTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export async function ssrTransform(
node.start,
`const ${spec.local.name} = ${importId}.${spec.imported.name}\n`
)
} else if (spec.type === 'ImportDefaultSpecifier') {
s.appendLeft(
node.start,
`const ${spec.local.name} = ${importId}.default\n`
)
}
}
s.remove(node.start, node.end)
Expand All @@ -55,9 +60,15 @@ export async function ssrTransform(
)
}
// TODO Class / Var
} else {
for (const spec of node.specifiers) {
s.append(`\n__exports__.${spec.exported.name} = ${spec.local.name}`)
}
s.remove(node.start, node.end)
}
}
if (node.type === 'ExportDefaultDeclaration') {
s.overwrite(node.start, node.start + 14, '__exports__.default =')
}
if (node.type === 'ExportAllDeclaration') {
}
Expand Down

0 comments on commit 7a15ada

Please sign in to comment.