Skip to content

Commit

Permalink
Don't error out when using Vite with RW v5 (#8040)
Browse files Browse the repository at this point in the history
* Don't error out when using Vite

* WIP vite prerendering

* Generate globalThis exports

* name space pages on globalThis

* Update a couple of comments

* Both auto-imported and manually imported pages now work with Vite

* runPrerender: Clean up and restructure handling of vite/webpack dependant code

* Cleanup

* Make things more webpack friendly again

* webpack: Generate chunk-references.json

* ChunkReferencesPlugin: Fix spelling

* Use data from new webpack plugin when prerendering

* Fix path issue when prerendering nested pages with Vite

* fix: change staticImports to prerender

* fix substring matches for prerender tests

---------

Co-authored-by: Dominic Saadi <dominiceliassaadi@gmail.com>
  • Loading branch information
Tobbe and jtoar authored Apr 25, 2023
1 parent 99f27ce commit 1590372
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 69 deletions.
8 changes: 7 additions & 1 deletion packages/core/config/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
const { merge } = require('webpack-merge')
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')

const { getWebSideDefaultBabelConfig } = require('@redwoodjs/internal')
const {
getWebSideDefaultBabelConfig,
} = require('@redwoodjs/internal/dist/build/babel/web')
const {
ChunkReferencesPlugin,
} = require('@redwoodjs/internal/dist/webpackPlugins/ChunkReferencesPlugin')
const { getConfig, getPaths } = require('@redwoodjs/project-config')

const redwoodConfig = getConfig()
Expand Down Expand Up @@ -261,6 +266,7 @@ module.exports = (webpackEnv) => {
new WebpackManifestPlugin({
fileName: 'build-manifest.json',
}),
isEnvProduction && new ChunkReferencesPlugin(),
...getSharedPlugins(isEnvProduction),
].filter(Boolean),
module: {
Expand Down
16 changes: 8 additions & 8 deletions packages/internal/src/__tests__/build_web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,27 @@ test('web files are prebuilt (no prerender)', async () => {
test('Check routes are imported with require when staticImports flag is enabled', () => {
const routesFile = getPaths().web.routes

const withStaticImports = prebuildWebFile(routesFile, {
staticImports: true,
const prerendered = prebuildWebFile(routesFile, {
prerender: true,
forJest: true,
}).code
})?.code

/* Check that imports have the form
`const HomePage = {
name: "HomePage",
loader: () => require("` 👈 Uses a require statement
*/
expect(withStaticImports).toContain(`const HomePage = {`)
expect(withStaticImports).toContain(`const BarPage = {`)
expect(prerendered).toContain(`const HomePage = {`)
expect(prerendered).toContain(`const BarPage = {`)

/*
👇 Foo page is an explicitly imported page in the source
const FooPage = {
name: "FooPage",
loader: () => require(
*/
expect(withStaticImports).toContain(`const FooPage = {`)
expect(withStaticImports).not.toContain(
expect(prerendered).toContain(`const FooPage = {`)
expect(prerendered).not.toContain(
`var _FooPage = _interopRequireDefault(require(`
)
})
Expand All @@ -89,7 +89,7 @@ test('Check routes are imported with "import" when staticImports flag is NOT pas

const withoutStaticImports = prebuildWebFile(routesFile, {
forJest: true,
}).code
})?.code

/* Check that imports have the form
`const HomePage = {
Expand Down
18 changes: 9 additions & 9 deletions packages/internal/src/__tests__/nestedPages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('User specified imports, with static imports', () => {

const routesFile = getPaths().web.routes
outputWithStaticImports = prebuildWebFile(routesFile, {
staticImports: true,
prerender: true,
forJest: true,
})?.code

Expand Down Expand Up @@ -57,15 +57,15 @@ describe('User specified imports, with static imports', () => {
`const LoginPage = {
name: "LoginPage",
loader: () => import( /* webpackChunkName: "LoginPage" */"./pages/LoginPage/LoginPage"),
prerenderLoader: () => require("./pages/LoginPage/LoginPage")
prerenderLoader: name => require("./pages/LoginPage/LoginPage")
}`
)

expect(outputWithStaticImports).toContain(
`const HomePage = {
name: "HomePage",
loader: () => import( /* webpackChunkName: "HomePage" */"./pages/HomePage/HomePage"),
prerenderLoader: () => require("./pages/HomePage/HomePage")
prerenderLoader: name => require("./pages/HomePage/HomePage")
}`
)
})
Expand All @@ -77,15 +77,15 @@ describe('User specified imports, with static imports', () => {
`const LoginPage = {
name: "LoginPage",
loader: () => import( /* webpackChunkName: "LoginPage" */"./pages/LoginPage/LoginPage"),
prerenderLoader: () => __webpack_require__(require.resolveWeak("./pages/LoginPage/LoginPage"))
prerenderLoader: name => __webpack_require__(require.resolveWeak("./pages/LoginPage/LoginPage"))
}`
)

expect(outputNoStaticImports).toContain(
`const HomePage = {
name: "HomePage",
loader: () => import( /* webpackChunkName: "HomePage" */"./pages/HomePage/HomePage"),
prerenderLoader: () => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage"))
prerenderLoader: name => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage"))
}`
)
})
Expand All @@ -100,7 +100,7 @@ describe('User specified imports, with static imports', () => {
`const NewJobPage = {
name: "NewJobPage",
loader: () => import( /* webpackChunkName: "NewJobPage" */"./pages/Jobs/NewJobPage/NewJobPage"),
prerenderLoader: () => require("./pages/Jobs/NewJobPage/NewJobPage")
prerenderLoader: name => require("./pages/Jobs/NewJobPage/NewJobPage")
}`
)
})
Expand All @@ -111,7 +111,7 @@ describe('User specified imports, with static imports', () => {
`const BazingaJobProfilePageWithFunnyName = {
name: "BazingaJobProfilePageWithFunnyName",
loader: () => import( /* webpackChunkName: "BazingaJobProfilePageWithFunnyName" */"./pages/Jobs/JobProfilePage/JobProfilePage"),
prerenderLoader: () => require("./pages/Jobs/JobProfilePage/JobProfilePage")
prerenderLoader: name => require("./pages/Jobs/JobProfilePage/JobProfilePage")
}`
)
})
Expand Down Expand Up @@ -153,7 +153,7 @@ describe('User specified imports, with static imports', () => {
expect(outputNoStaticImports).toContain(`const HomePage = {
name: "HomePage",
loader: () => import( /* webpackChunkName: "HomePage" */"./pages/HomePage/HomePage"),
prerenderLoader: () => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage"))
prerenderLoader: name => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage"))
}`)
expect(outputNoStaticImports).toContain(`.createElement(_router.Route, {
path: "/",
Expand Down Expand Up @@ -188,7 +188,7 @@ describe('User specified imports, with static imports', () => {
expect(outputWithStaticImports).toContain(`const EditJobPage = {
name: "EditJobPage",
loader: () => import( /* webpackChunkName: "EditJobPage" */"./pages/Jobs/EditJobPage/EditJobPage"),
prerenderLoader: () => require("./pages/Jobs/EditJobPage/EditJobPage")
prerenderLoader: name => require("./pages/Jobs/EditJobPage/EditJobPage")
}`)

expect(outputNoStaticImports).toContain(
Expand Down
18 changes: 12 additions & 6 deletions packages/internal/src/build/babel/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ export const getWebSideBabelPlugins = (
}

export const getWebSideOverrides = (
{ staticImports }: Flags = {
staticImports: false,
{ prerender, forVite }: Flags = {
prerender: false,
forVite: false,
}
) => {
const overrides = [
Expand All @@ -119,7 +120,8 @@ export const getWebSideOverrides = (
require('../babelPlugins/babel-plugin-redwood-routes-auto-loader')
.default,
{
useStaticImports: staticImports,
prerender,
vite: forVite,
},
],
],
Expand Down Expand Up @@ -200,7 +202,7 @@ export const getWebSideBabelConfigPath = () => {
// These flags toggle on/off certain features
export interface Flags {
forJest?: boolean // will change the alias for module-resolver plugin
staticImports?: boolean // will use require instead of import for routes-auto-loader plugin
prerender?: boolean // changes what babel-plugin-redwood-routes-auto-loader does
forVite?: boolean
}

Expand All @@ -221,9 +223,10 @@ export const getWebSideDefaultBabelConfig = (options: Flags = {}) => {

// Used in prerender only currently
export const registerWebSideBabelHook = ({
forVite = false,
plugins = [],
overrides = [],
}: RegisterHookOptions = {}) => {
}: RegisterHookOptions & { forVite?: boolean } = {}) => {
const defaultOptions = getWebSideDefaultBabelConfig()
registerBabel({
...defaultOptions,
Expand All @@ -233,7 +236,10 @@ export const registerWebSideBabelHook = ({
cache: false,
// We only register for prerender currently
// Static importing pages makes sense
overrides: [...getWebSideOverrides({ staticImports: true }), ...overrides],
overrides: [
...getWebSideOverrides({ prerender: true, forVite }),
...overrides,
],
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('page auto loader correctly imports pages', () => {
expect(result?.code).toContain(`const HomePage = {
name: "HomePage",
loader: () => import( /* webpackChunkName: "HomePage" */"./pages/HomePage/HomePage"),
prerenderLoader: () => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage"))`)
prerenderLoader: name => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage"))`)
})

test('Already imported pages are left alone.', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from '@redwoodjs/project-config'

interface PluginOptions {
useStaticImports?: boolean
prerender?: boolean
vite?: boolean
}

/**
Expand All @@ -37,7 +38,7 @@ const withRelativeImports = (page: PagesDependency) => {

export default function (
{ types: t }: { types: typeof types },
{ useStaticImports = false }: PluginOptions
{ prerender = false, vite = false }: PluginOptions
): PluginObj {
// @NOTE: This var gets mutated inside the visitors
let pages = processPagesDir().map(withRelativeImports)
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function (
// This is to make sure that all the imported "Page modules" are normal
// imports and not asynchronous ones.
// Note that jest in a user's project does not enter this block, but our tests do
if (useStaticImports) {
if (prerender) {
// Match import paths, const name could be different

const pageThatUserImported = pages.find((page) => {
Expand Down Expand Up @@ -118,10 +119,7 @@ export default function (
// + const <importName> = {
// name: <importName>,
// loader: () => import(/* webpackChunkName: "<importName>" */ <relativeImportPath>)
// // prerender
// prerenderLoader: () => require(<relativeImportPath>)
// // crs
// prerenderLoader: () => __webpack_require__(require.resolveWeak(<relativeImportPath>))
// prerenderLoader: (name) => prerenderLoaderImpl
// }

const importArgument = t.stringLiteral(relativeImport)
Expand All @@ -143,6 +141,7 @@ export default function (
t.stringLiteral(importName)
),
// loader for dynamic imports (browser)
// loader: () => import(<importArgument>)
t.objectProperty(
t.identifier('loader'),
t.arrowFunctionExpression(
Expand All @@ -154,38 +153,67 @@ export default function (
),
// prerenderLoader for ssr/prerender and first load of
// prerendered pages in browser (csr)
// prerenderLoader: (name) => { prerenderLoaderImpl }
t.objectProperty(
t.identifier('prerenderLoader'),
t.arrowFunctionExpression(
[],
useStaticImports
? t.callExpression(t.identifier('require'), [
t.stringLiteral(relativeImport),
])
: t.callExpression(
t.identifier(
// Use __webpack_require__ otherwise all pages will
// be bundled
'__webpack_require__'
),
[
t.callExpression(
t.identifier('require.resolveWeak'),
[t.stringLiteral(relativeImport)]
),
]
)
[t.identifier('name')],
prerenderLoaderImpl(prerender, vite, relativeImport, t)
)
),
])
),
])
)
}

// Insert at the top of the file
p.node.body.unshift(...nodes)
},
},
},
}
}

function prerenderLoaderImpl(
prerender: boolean,
vite: boolean,
relativeImport: string,
t: typeof types
) {
if (prerender) {
// This works for both vite and webpack
return t.callExpression(t.identifier('require'), [
t.stringLiteral(relativeImport),
])
}

// This code will be output when building the web side (i.e. not when
// prerendering)
// active-route-loader will use this code for auto-imported pages, for the
// first load of a prerendered page
// Manually imported pages will be bundled in the main bundle and will be
// loaded by the code in `normalizePage` in util.ts
let implForBuild
if (vite) {
implForBuild = t.objectExpression([
t.objectProperty(
t.identifier('default'),
t.memberExpression(
t.identifier('globalThis.__REDWOOD__PRERENDER_PAGES'),
t.identifier('name'),
true
)
),
])
} else {
// Use __webpack_require__ otherwise all pages will be bundled
implForBuild = t.callExpression(t.identifier('__webpack_require__'), [
t.callExpression(t.identifier('require.resolveWeak'), [
t.stringLiteral(relativeImport),
]),
])
}

return implForBuild
}
44 changes: 44 additions & 0 deletions packages/internal/src/webpackPlugins/ChunkReferencesPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Compiler, Chunk } from 'webpack'

export class ChunkReferencesPlugin {
static defaultOptions = {
outputFile: 'chunk-references.json',
}

options: typeof ChunkReferencesPlugin.defaultOptions

constructor(options = {}) {
this.options = { ...ChunkReferencesPlugin.defaultOptions, ...options }
}

apply(compiler: Compiler) {
compiler.hooks.emit.tap('ChunkReferencesPlugin', (compilation) => {
const output: Array<{
name: string
id: string | number
files: Array<string>
referencedChunks: Array<string | number>
}> = []

compilation.chunks.forEach((chunk) => {
if (chunk.id) {
output.push({
name: chunk.name,
id: chunk.id,
files: Array.from(chunk.files).map((f) => '/' + f),
referencedChunks: Array.from(chunk.getAllReferencedChunks())
.filter((c): c is Chunk & { id: string | number } => {
return !!c.id && c.id !== chunk.id
})
.map((referencedChunk) => referencedChunk.id),
})
}
})

compilation.emitAsset(
this.options.outputFile,
new compiler.webpack.sources.RawSource(JSON.stringify(output, null, 2))
)
})
}
}
Loading

0 comments on commit 1590372

Please sign in to comment.