Skip to content

Commit

Permalink
Merge pull request #3440 from owid/vite-separate-builds
Browse files Browse the repository at this point in the history
chore(vite): use different configs for admin and site; don't split assets into chunks
  • Loading branch information
marcelgerber authored Apr 8, 2024
2 parents 404feaf + fa86c48 commit fd61589
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 144 deletions.
8 changes: 4 additions & 4 deletions .bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"files": [
{
"path": "./dist/assets/common.mjs",
"maxSize": "2.2MB"
"path": "./dist/assets/owid.mjs",
"maxSize": "2.4MB"
},
{
"path": "./dist/assets/owid.mjs",
"maxSize": "450KB"
"path": "./dist/assets/owid.css",
"maxSize": "430KB"
}
],
"defaultCompression": "none"
Expand Down
4 changes: 2 additions & 2 deletions adminSiteServer/IndexPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {
GITHUB_USERNAME,
DATA_API_FOR_ADMIN_UI,
} from "../settings/serverSettings.js"
import { VITE_ASSET_ADMIN_ENTRY, viteAssets } from "../site/viteUtils.js"
import { viteAssetsForAdmin } from "../site/viteUtils.js"

export const IndexPage = (props: {
username: string
isSuperuser: boolean
gitCmsBranchName: string
}) => {
const assets = viteAssets(VITE_ASSET_ADMIN_ENTRY)
const assets = viteAssetsForAdmin()
const script = `
window.isEditor = true
window.admin = new Admin({ username: "${
Expand Down
1 change: 1 addition & 0 deletions adminSiteServer/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class OwidAdminApp {
app.use("/fonts", express.static("public/fonts"))

app.use("/api", publicApiRouter.router)
app.use("/assets-admin", express.static("dist/assets-admin"))
app.use("/admin/api", apiRouter.router)
app.use("/admin/test", testPageRouter)
app.use("/admin/storybook", express.static(".storybook/build"))
Expand Down
4 changes: 4 additions & 0 deletions baker/SiteBaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,10 @@ export class SiteBaker {
await execWrapper(
`rm -rf ${this.bakedSiteDir}/assets && cp -r ${BASE_DIR}/dist/assets ${this.bakedSiteDir}/assets`
)
await execWrapper(
`rm -rf ${this.bakedSiteDir}/assets-admin && cp -r ${BASE_DIR}/dist/assets-admin ${this.bakedSiteDir}/assets-admin`
)

await this.validateTagIcons(trx)
await execWrapper(
`rsync -hav --delete ${BASE_DIR}/public/* ${this.bakedSiteDir}/ ${excludes}`
Expand Down
4 changes: 2 additions & 2 deletions devTools/vite/startVite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

if [ "$VITE_PREVIEW" = "true" ]; then
echo "Starting Vite build for preview mode"
yarn vite build
yarn buildVite
else
echo "Starting Vite in dev mode"
yarn vite dev
yarn vite dev --config vite.config-site.mts
fi
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"buildLocalBake": "node --enable-source-maps ./itsJustJavascript/baker/buildLocalBake.js",
"buildTsc": "tsc -b -verbose",
"buildLerna": "lerna run build",
"buildVite": "vite build",
"buildViteAdmin": "vite build --config vite.config-admin.mts",
"buildViteSite": "vite build --config vite.config-site.mts",
"buildVite": "yarn buildViteSite && yarn buildViteAdmin",
"buildWordpressPlugin": "cd ./wordpress/web/app/plugins/owid && yarn && yarn build",
"bakeGdocPosts": "node --enable-source-maps ./itsJustJavascript/baker/bakeGdocPosts.js",
"cleanTsc": "rm -rf itsJustJavascript && tsc -b -clean",
Expand All @@ -40,7 +42,7 @@
"startWordpressPlugin": "cd ./wordpress/web/app/plugins/owid && yarn && yarn start",
"startSiteFront": "./devTools/vite/startVite.sh",
"startLocalBakeServer": "http-server ./localBake -p 3000",
"testBundlewatch": "yarn buildVite && bundlewatch --config .bundlewatch.config.json",
"testBundlewatch": "yarn buildViteSite && bundlewatch --config .bundlewatch.config.json",
"testCypress": "yarn cypress open --project devTools --config-file cypress/cypress.json",
"testLint": "eslint .",
"testPrettierAll": "yarn prettier --check \"**/*.{tsx,ts,jsx,js,json,md,html,css,scss,yml}\"",
Expand Down
4 changes: 2 additions & 2 deletions site/Head.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"
import { VITE_ASSET_SITE_ENTRY, viteAssets } from "./viteUtils.js"
import { viteAssetsForSite } from "./viteUtils.js"
import {
GOOGLE_TAG_MANAGER_ID,
BAKED_BASE_URL,
Expand Down Expand Up @@ -59,7 +59,7 @@ export const Head = (props: {
href: "/atom.xml",
}

const stylesheets = viteAssets(VITE_ASSET_SITE_ENTRY).forHeader
const stylesheets = viteAssetsForSite().forHeader

return (
<head>
Expand Down
4 changes: 2 additions & 2 deletions site/SiteFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
import { faAngleRight } from "@fortawesome/free-solid-svg-icons"
import { SiteFooterContext } from "@ourworldindata/utils"
import { VITE_ASSET_SITE_ENTRY, viteAssets } from "./viteUtils.js"
import { viteAssetsForSite } from "./viteUtils.js"

interface SiteFooterProps {
hideDonate?: boolean
Expand Down Expand Up @@ -300,7 +300,7 @@ export const SiteFooter = (props: SiteFooterProps) => (
</div>
</div>
<div className="site-tools" />
{viteAssets(VITE_ASSET_SITE_ENTRY).forFooter}
{viteAssetsForSite().forFooter}
<script
type="module"
dangerouslySetInnerHTML={{
Expand Down
59 changes: 45 additions & 14 deletions site/viteUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,31 @@ import {
import { POLYFILL_URL } from "./SiteConstants.js"
import type { Manifest } from "vite"
import { sortBy } from "@ourworldindata/utils"
import urljoin from "url-join"

const VITE_DEV_URL = process.env.VITE_DEV_URL ?? "http://localhost:8090"

export const VITE_ASSET_SITE_ENTRY = "site/owid.entry.ts"
export const VITE_ASSET_ADMIN_ENTRY = "adminSiteClient/admin.entry.ts"

export enum ViteEntryPoint {
Site = "site",
Admin = "admin",
}

export const VITE_ENTRYPOINT_INFO = {
[ViteEntryPoint.Site]: {
entryPointFile: VITE_ASSET_SITE_ENTRY,
outDir: "assets",
outName: "owid",
},
[ViteEntryPoint.Admin]: {
entryPointFile: VITE_ASSET_ADMIN_ENTRY,
outDir: "assets-admin",
outName: "admin",
},
}

// We ALWAYS load polyfills.

const polyfillScript = <script key="polyfill" src={POLYFILL_URL} />
Expand All @@ -33,7 +52,7 @@ interface Assets {
}

// in dev: we need to load several vite core scripts and plugins; other than that we only need to load the entry point, and vite will take care of the rest.
const devAssets = (entry: string, baseUrl: string): Assets => {
const devAssets = (entrypoint: ViteEntryPoint, baseUrl: string): Assets => {
return {
forHeader: [polyfillPreload],
forFooter: [
Expand All @@ -59,12 +78,16 @@ const devAssets = (entry: string, baseUrl: string): Assets => {
type="module"
src={`${baseUrl}/@vite/client`}
/>,
<script key={entry} type="module" src={`${baseUrl}/${entry}`} />,
<script
key={entrypoint}
type="module"
src={`${baseUrl}/${VITE_ENTRYPOINT_INFO[entrypoint].entryPointFile}`}
/>,
],
}
}

// Goes through the manifest.json file that vite creates, finds all the assets that are required for the entry point,
// Goes through the manifest.json files that vite creates, finds all the assets that are required for the given entry point,
// and creates the appropriate <link> and <script> tags for them.
export const createTagsForManifestEntry = (
manifest: Manifest,
Expand All @@ -80,7 +103,7 @@ export const createTagsForManifestEntry = (
if (!manifestEntry)
throw new Error(`Could not find manifest entry for ${entry}`)

const assetUrl = `${assetBaseUrl}${manifestEntry.file}`
const assetUrl = urljoin(assetBaseUrl, manifestEntry.file)

if (entry.endsWith(".css")) {
assets = [
Expand Down Expand Up @@ -132,22 +155,27 @@ export const createTagsForManifestEntry = (

// in prod: we need to make sure that we include <script> and <link> tags that are required for the entry point.
// this could be, for example: owid.mjs, common.mjs, owid.css, common.css. (plus Google Fonts and polyfills)
const prodAssets = (entry: string, baseUrl: string): Assets => {
const prodAssets = (entrypoint: ViteEntryPoint, baseUrl: string): Assets => {
const baseDir = findBaseDir(__dirname)
const manifestPath = `${baseDir}/dist/manifest.json`
const entrypointInfo = VITE_ENTRYPOINT_INFO[entrypoint]
const manifestPath = `${baseDir}/dist/${entrypointInfo.outDir}/manifest.json`
let manifest
try {
manifest = fs.readJSONSync(manifestPath) as Manifest
manifest = fs.readJsonSync(manifestPath) as Manifest
} catch (err) {
throw new Error(
`Could not read build manifest ('${manifestPath}'), which is required for production.
`Could not read the build manifest ('${manifestPath}'), which is required for production.
If you're running in VITE_PREVIEW mode, wait for the build to finish and then reload this page.`,
{ cause: err }
)
}

const assetBaseUrl = `${baseUrl}/`
const assets = createTagsForManifestEntry(manifest, entry, assetBaseUrl)
const assetBaseUrl = `${baseUrl}/${entrypointInfo.outDir}/`
const assets = createTagsForManifestEntry(
manifest,
entrypointInfo.entryPointFile,
assetBaseUrl
)

return {
// sort for some kind of consistency: first modulepreload, then preload, then stylesheet
Expand All @@ -158,14 +186,17 @@ const prodAssets = (entry: string, baseUrl: string): Assets => {

const useProductionAssets = ENV === "production" || VITE_PREVIEW

export const viteAssets = (entry: string, prodBaseUrl?: string) =>
const viteAssets = (entrypoint: ViteEntryPoint, prodBaseUrl?: string) =>
useProductionAssets
? prodAssets(entry, prodBaseUrl ?? "")
: devAssets(entry, VITE_DEV_URL)
? prodAssets(entrypoint, prodBaseUrl ?? "")
: devAssets(entrypoint, VITE_DEV_URL)

export const viteAssetsForAdmin = () => viteAssets(ViteEntryPoint.Admin)
export const viteAssetsForSite = () => viteAssets(ViteEntryPoint.Site)

export const generateEmbedSnippet = () => {
// Make sure we're using an absolute URL here, since we don't know in what context the embed snippet is used.
const assets = viteAssets(VITE_ASSET_SITE_ENTRY, BAKED_BASE_URL)
const assets = viteAssets(ViteEntryPoint.Site, BAKED_BASE_URL)

const serializedAssets = [...assets.forHeader, ...assets.forFooter].map(
(el) => ({
Expand Down
4 changes: 4 additions & 0 deletions vite.config-admin.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ViteEntryPoint } from "./site/viteUtils.tsx"
import { defineViteConfigForEntrypoint } from "./vite.config-common.mts"

export default defineViteConfigForEntrypoint(ViteEntryPoint.Admin)
88 changes: 88 additions & 0 deletions vite.config-common.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { defineConfig } from "vite"
import pluginReact from "@vitejs/plugin-react"
import pluginChecker from "vite-plugin-checker"
import { warmup as pluginWarmup } from "vite-plugin-warmup"
import * as clientSettings from "./settings/clientSettings.js"
import {
VITE_ASSET_SITE_ENTRY,
VITE_ENTRYPOINT_INFO,
ViteEntryPoint,
} from "./site/viteUtils.js"

// https://vitejs.dev/config/
export const defineViteConfigForEntrypoint = (entrypoint: ViteEntryPoint) => {
const entrypointInfo = VITE_ENTRYPOINT_INFO[entrypoint]

return defineConfig({
publicDir: false, // don't copy public folder to dist
resolve: {
alias: {
"@ourworldindata/grapher/src": "@ourworldindata/grapher/src", // need this for imports of @ourworldindata/grapher/src/core/grapher.scss to work

// we alias to the packages source files in dev and prod:
// this means we get instant dev updates when we change one of them,
// and the prod build builds them all as esm modules, which helps with tree shaking
// Idea from https://github.com/LinusBorg/vue-lib-template/blob/3775e49b20a7c3349dd49321cad2ed7f9d575057/packages/playground/vite.config.ts
"@ourworldindata/grapher":
"@ourworldindata/grapher/src/index.ts",
"@ourworldindata/utils": "@ourworldindata/utils/src/index.ts",
"@ourworldindata/types": "@ourworldindata/types/src/index.ts",
"@ourworldindata/core-table":
"@ourworldindata/core-table/src/index.ts",
"@ourworldindata/components":
"@ourworldindata/components/src/index.ts",
},
},
css: {
devSourcemap: true,
},
define: {
// Replace all clientSettings with their respective values, i.e. assign e.g. BUGNSAG_API_KEY to process.env.BUGNSAG_API_KEY
// it's important to note that we only expose values that are present in the clientSettings file - not any other things that are stored in .env
...Object.fromEntries(
Object.entries(clientSettings).map(([key, value]) => [
`process.env.${key}`,
JSON.stringify(value),
])
),
},
build: {
manifest: true, // creates a manifest.json file, which we use to determine which files to load in prod
emptyOutDir: true,
outDir: `dist/${entrypointInfo.outDir}`,
sourcemap: true,
target: ["chrome66", "firefox78", "safari12"], // see docs/browser-support.md
rollupOptions: {
input: {
[entrypointInfo.outName]: entrypointInfo.entryPointFile,
},
output: {
assetFileNames: `${entrypointInfo.outName}.css`,
entryFileNames: `${entrypointInfo.outName}.mjs`,
},
},
},
plugins: [
pluginReact({
babel: {
parserOpts: {
plugins: ["decorators-legacy"], // needed so mobx decorators work correctly
},
},
}),
pluginChecker({
typescript: {
buildMode: true,
tsconfigPath: "tsconfig.vite-checker.json",
},
}),
pluginWarmup({ clientFiles: [VITE_ASSET_SITE_ENTRY] }),
],
server: {
port: 8090,
},
preview: {
port: 8090,
},
})
}
4 changes: 4 additions & 0 deletions vite.config-site.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ViteEntryPoint } from "./site/viteUtils.tsx"
import { defineViteConfigForEntrypoint } from "./vite.config-common.mts"

export default defineViteConfigForEntrypoint(ViteEntryPoint.Site)
Loading

0 comments on commit fd61589

Please sign in to comment.