Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't error out when using Vite with RW v5 #8040

Merged
merged 20 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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