Réorganise les fichiers pour le build

pull/2799/head
Johan Girod 2023-09-12 09:37:17 +02:00
parent 7508d30f71
commit 4eac3be829
14 changed files with 201 additions and 166 deletions

View File

@ -0,0 +1,5 @@
# We overwrite the default ESLint configuration for NodeJS scripts
env:
node: true
rules:
no-console: 0

119
site/build/multiple-SPA.ts Normal file
View File

@ -0,0 +1,119 @@
import fs from 'fs/promises'
import serveStatic from 'serve-static'
import { Plugin } from 'vite'
type MultipleSPAOptions = {
defaultSite: string
templatePath: string
sites: Record<string, Record<string, string>>
}
/**
* A custom plugin to create multiple virtual html files from a template. Will
* generate distinct entry points and single-page application outputs.
*/
export function multipleSPA(options: MultipleSPAOptions): Plugin {
const fillTemplate = async (siteName: string) => {
const siteData = options.sites[siteName]
const template = await fs.readFile(options.templatePath, 'utf-8')
const filledTemplate = template
.toString()
.replace(/\{\{(.+)\}\}/g, (_match, p1) => siteData[(p1 as string).trim()])
return filledTemplate
}
return {
name: 'multiple-spa',
enforce: 'pre',
configureServer(vite) {
// TODO: this middleware is specific to the "mon-entreprise" app and
// shouldn't be in the "multipleSPA" plugin. It allows to serve the
// iframe integration script (already built) from the same server as the app.
vite.middlewares.use(
'/simulateur-iframe-integration.js',
serveStatic(new URL('./dist', import.meta.url).pathname, {
index: 'simulateur-iframe-integration.js',
})
)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
vite.middlewares.use(async (req, res, next) => {
const url = req.originalUrl?.replace(/^\/%2F/, '/')
const firstLevelDir = url?.slice(1).split('/')[0]
if (url && /\?.*html-proxy/.test(url)) {
return next()
}
if (url && ['/', '/index.html'].includes(url)) {
res.writeHead(302, { Location: '/' + options.defaultSite }).end()
}
// this condition is for the start:netlify script to match /mon-entreprise or /infrance
else if (
firstLevelDir &&
url &&
Object.keys(options.sites)
.map((site) => `/${site}.html`)
.includes(url)
) {
const siteName = firstLevelDir.replace('.html', '')
const content = await vite.transformIndexHtml(
'/' + siteName,
await fillTemplate(siteName)
)
res.end(content)
} else if (
firstLevelDir &&
Object.keys(options.sites).some((name) => firstLevelDir === name)
) {
const siteName = firstLevelDir
const content = await vite.transformIndexHtml(
url,
await fillTemplate(siteName)
)
res.end(content)
} else {
next()
}
})
},
config(config, { command }) {
if (command === 'build' && !config.build?.ssr) {
config.build = {
...config.build,
rollupOptions: {
...config.build?.rollupOptions,
input: Object.fromEntries(
Object.keys(options.sites).map((name) => [
name,
`virtual:${name}.html`,
])
),
},
}
}
},
resolveId(id) {
const pathname = id.split('/').slice(-1)[0]
if (pathname?.startsWith('virtual:')) {
return pathname.replace('virtual:', '')
}
return null
},
async load(id) {
if (
Object.keys(options.sites).some((name) => id.endsWith(name + '.html'))
) {
return await fillTemplate(id.replace(/\.html$/, ''))
}
},
}
}

View File

@ -2,7 +2,7 @@ import { promises as fs, readFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'url'
import { render } from './dist/ssr/entry-server.js'
import { render } from '../dist/ssr/entry-server.js'
const dirname = path.dirname(fileURLToPath(import.meta.url))
@ -22,7 +22,7 @@ export default async ({ site, url, lang }) => {
const template =
cache[site] ??
readFileSync(path.join(dirname, `./dist/${site}.html`), 'utf-8')
readFileSync(path.join(dirname, `../dist/${site}.html`), 'utf-8')
cache[site] = template
@ -31,10 +31,10 @@ export default async ({ site, url, lang }) => {
.replace('<!--app-style-->', styleTags)
.replace(regexHelmet, helmet.title.toString() + helmet.meta.toString())
const dir = path.join(dirname, 'dist/prerender', site, decodeURI(url))
const dir = path.join(dirname, '../dist/prerender', site, decodeURI(url))
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(path.join(dir, 'index.html'), page)
return path.relative(path.join(dirname, 'dist'), path.join(dir, 'index.html'))
return path.relative(path.join(dirname, '../dist'), path.join(dir, 'index.html'))
}

View File

@ -4,7 +4,7 @@ import { argv } from 'node:process'
import Tinypool from 'tinypool'
import { absoluteSitePaths } from './source/sitePaths.js'
import { absoluteSitePaths } from '../source/sitePaths.js'
const filename = new URL('./prerender-worker.js', import.meta.url).href
const pool = new Tinypool({ filename })

View File

@ -1,11 +1,11 @@
import { unlinkSync, writeFileSync } from 'fs'
import path from 'path'
import { writeFileSync } from 'fs'
import { join, resolve } from 'path'
import { defineConfig } from 'vite'
import { PageConfig } from '@/pages/simulateurs/_configs/types'
import { objectTransform } from './source/utils'
import { objectTransform } from '../source/utils'
const filterOgImage = (obj: Record<string, Omit<PageConfig, 'component'>>) =>
objectTransform(obj, (entries) => {
@ -25,10 +25,10 @@ const filterOgImage = (obj: Record<string, Omit<PageConfig, 'component'>>) =>
export default defineConfig({
resolve: {
alias: [{ find: '@', replacement: path.resolve('./source') }],
alias: [{ find: '@', replacement: resolve('./source') }],
},
build: {
outDir: './',
outDir: './dist',
target: 'esnext',
emptyOutDir: false,
lib: {
@ -60,13 +60,16 @@ export default defineConfig({
closeBundle: () => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(async () => {
const path = './builded-simulation-data.js'
const path = join(
import.meta.url,
'../../dist/builded-simulation-data.js'
)
console.log('path', path)
type PageConfigType = {
default: Record<string, Omit<PageConfig, 'component'>>
}
const algoliaUpdate = ((await import(path)) as PageConfigType).default
unlinkSync(path)
writeFileSync(
'./source/public/simulation-data.json',
JSON.stringify(filterOgImage(algoliaUpdate))

View File

@ -18,35 +18,42 @@
"not ie < 11"
],
"scripts": {
"build:yaml-to-dts": "ts-node-esm scripts/build-yaml-to-dts.ts",
"postinstall": "node scripts/prepare.js",
"start": "vite dev",
"start:axe-debugging": "VITE_AXE_CORE_ENABLED=true vite dev",
"start:netlify": "sed 's|:SITE_EN|/infrance|g' netlify.base.toml | sed 's|:SITE_FR|/mon-entreprise|g' | sed 's|:API_URL|http://localhost:3004|g' | sed 's|\\[\\[redirects\\]\\]|\\[\\[redirects\\]\\]\\n force = true|g' > netlify.toml && HMR_CLIENT_PORT=8888 netlify dev",
"start:storybook": "storybook dev -p 6006",
"postinstall": "node scripts/prepare.js",
"build": "NODE_OPTIONS='--max-old-space-size=6144'; yarn build:sitemap && yarn build:simulator-data && vite build && yarn build:iframe-script",
"build:ssr": "NODE_OPTIONS='--max-old-space-size=4096'; vite build --ssr ./source/entries/entry-server.tsx --outDir ./dist/ssr --emptyOutDir && echo '{\"type\": \"module\"}' > dist/package.json",
"build:prerender": "ts-node-esm prerender.ts",
"build:simulator-data": "vite build --config vite-build-simulation-data.config.ts",
"build:iframe-script": "NODE_OPTIONS='--max-old-space-size=4096'; vite build --config vite-iframe-script.config.ts",
"build:sitemap": "ts-node-esm scripts/build-sitemap.ts",
"build:iframe-script": "NODE_OPTIONS='--max-old-space-size=4096'; vite build --config build/vite-iframe-script.config.ts",
"build:prerender": "ts-node-esm build/prerender.ts",
"build:preview": "VITE_FR_BASE_URL=http://localhost:8888; VITE_EN_BASE_URL=http://localhost:8889; yarn build && yarn build:ssr && cp ./netlify.base.toml ./netlify.preview.toml && yarn build:prerender --dev --netlify-toml-path ./netlify.preview.toml",
"build:simulator-data": "vite build --config build/vite-build-simulation-data.config.ts",
"build:sitemap": "ts-node-esm build/build-sitemap.ts",
"build:ssr": "NODE_OPTIONS='--max-old-space-size=4096'; vite build --ssr ./source/entries/entry-server.tsx --outDir ./dist/ssr --emptyOutDir && echo '{\"type\": \"module\"}' > dist/package.json",
"build:storybook": "NODE_OPTIONS='--max-old-space-size=6144'; storybook build",
"build:yaml-to-dts": "ts-node-esm scripts/build-yaml-to-dts.ts",
"preview:mon-entreprise": "sed 's|:SITE_EN|_|g' netlify.preview.toml | sed 's|:SITE_FR||g' | sed 's|:API_URL|http://localhost:3004|g' > dist/netlify.toml && cd dist && netlify dev -d ./ -p 8888",
"preview:infrance": " sed 's|:SITE_EN||g' netlify.preview.toml | sed 's|:SITE_FR|_|g' | sed 's|:API_URL|http://localhost:3004|g' > dist/netlify.toml && cd dist && netlify dev -d ./ -p 8889",
"typecheck:watch": "tsc --skipLibCheck --noEmit --watch",
"test": "vitest",
"test:dev-e2e:mon-entreprise": "cypress open --e2e --config \"baseUrl=http://localhost:8888\"",
"test:dev-e2e:mycompanyinfrance": "cypress open --e2e --config \"baseUrl=http://localhost:8889,specPattern=cypress/integration/mon-entreprise/english/**/*.{js,jsx,ts,tsx}\" --env language=en",
"test:record-http-calls:mon-entreprise": "cypress run --env record_http=",
"algolia:update": "yarn build:simulator-data && ts-node-esm ./scripts/search/update-data.ts",
"algolia:clean": "node scripts/search/clean.js",
"algolia:update": "yarn build:simulator-data && ts-node-esm ./scripts/search/update-data.ts",
"i18n:check": "yarn i18n:rules:check && yarn i18n:ui:check",
"i18n:translate": "yarn i18n:rules:translate && yarn i18n:ui:translate",
"i18n:rules:check": "node scripts/i18n/check-missing-rule-translation.js",
"i18n:rules:translate": "node scripts/i18n/translate-rules.js",
"i18n:ui:check": "i18next -c scripts/i18n/parser.config.js && node scripts/i18n/check-missing-UI-translation.js",
"i18n:ui:translate": "rm -rf source/locales/static-analysis-fr.json && i18next -c scripts/i18n/parser.config.js && node scripts/i18n/translate-ui.js",
"start:storybook": "storybook dev -p 6006",
"build:storybook": "NODE_OPTIONS='--max-old-space-size=6144'; storybook build"
"i18n:ui:translate": "rm -rf source/locales/static-analysis-fr.json && i18next -c scripts/i18n/parser.config.js && node scripts/i18n/translate-ui.js"
},
"dependencies": {
"@atomik-color/component": "^1.0.17",

View File

@ -1,6 +1,6 @@
import { execOnFileChange } from './execOnFileChange.js'
export const runScriptOnFileChange = async () => {
export const compileEconomieCollaborativeYaml = async () => {
console.log('Search for changed file...')
const results = await execOnFileChange({

View File

@ -31,12 +31,13 @@
"include": [
"source",
"scripts",
"build",
"test/**/*.ts",
"vite.config.ts",
"cypress.config.ts",
"vite-iframe-script.config.ts",
"vite-build-simulation-data.config.ts",
"prerender.ts",
"build/vite-build-simulation-data.config.ts",
"build/prerender.ts",
"vite-pwa-options.ts"
]
}

View File

@ -1,17 +1,17 @@
/* eslint-disable no-console */
import fs from 'fs/promises'
import path from 'path'
import replace from '@rollup/plugin-replace'
import yaml, { ValidYamlType } from '@rollup/plugin-yaml'
import legacy from '@vitejs/plugin-legacy'
import react from '@vitejs/plugin-react-swc'
import serveStatic from 'serve-static'
import { defineConfig, loadEnv, Plugin } from 'vite'
import { defineConfig, loadEnv } from 'vite'
import { VitePWA } from 'vite-plugin-pwa'
import { runScriptOnFileChange } from './scripts/runScriptOnFileChange'
import { pwaOptions } from './vite-pwa-options'
import { multipleSPA } from './build/multiple-SPA'
import { pwaOptions } from './build/vite-pwa-options'
import { compileEconomieCollaborativeYaml } from './scripts/compileEconomieColllaborativeYaml'
const env = (mode: string) => loadEnv(mode, process.cwd(), '')
@ -47,18 +47,30 @@ export default defineConfig(({ command, mode }) => ({
apply: 'serve',
buildStart() {
if (mode === 'development') {
void runScriptOnFileChange()
void compileEconomieCollaborativeYaml()
}
},
},
command === 'build' &&
replace({
__SENTRY_DEBUG__: false,
preventAssignment: false,
}),
react({
plugins: [['@swc/plugin-styled-components', { pure: true }]],
plugins: [
[
'@swc/plugin-styled-components',
{
pure: true,
displayName: true,
transpileTemplateLiterals: false,
},
],
],
}),
yaml({
transform(data, filePath) {
return filePath.endsWith('/rules-en.yaml')
@ -66,6 +78,7 @@ export default defineConfig(({ command, mode }) => ({
: data
},
}),
multipleSPA({
defaultSite: 'mon-entreprise',
templatePath: './source/entries/template.html',
@ -84,11 +97,14 @@ export default defineConfig(({ command, mode }) => ({
},
},
}),
VitePWA(pwaOptions),
legacy({
targets: ['defaults', 'not IE 11'],
}),
],
server: {
port: 3000,
hmr: {
@ -116,150 +132,22 @@ export default defineConfig(({ command, mode }) => ({
},
},
},
optimizeDeps: {
entries: ['./source/entries/entry-fr.tsx', './source/entries/entry-en.tsx'],
include: ['publicodes-react > react/jsx-runtime'],
exclude: ['publicodes-react', 'publicodes'],
},
ssr: {
/**
* Prevent listed dependencies from being externalized for SSR build cause some
* packages are not esm ready or package.json setup seems wrong, wait this pr to be merge:
* markdown-to-jsx: https://github.com/probablyup/markdown-to-jsx/pull/414
* styled-components: https://github.com/styled-components/styled-components/issues/3601 (wait v6 release)
* packages are not esm ready or package.json setup seems wrong:
*/
noExternal: [/styled-components|emotion/, /tslib/],
noExternal: [/tslib/],
},
}))
type MultipleSPAOptions = {
defaultSite: string
templatePath: string
sites: Record<string, Record<string, string>>
}
/**
* A custom plugin to create multiple virtual html files from a template. Will
* generate distinct entry points and single-page application outputs.
*/
function multipleSPA(options: MultipleSPAOptions): Plugin {
const fillTemplate = async (siteName: string) => {
const siteData = options.sites[siteName]
const template = await fs.readFile(options.templatePath, 'utf-8')
const filledTemplate = template
.toString()
.replace(/\{\{(.+)\}\}/g, (_match, p1) => siteData[(p1 as string).trim()])
return filledTemplate
}
return {
name: 'multiple-spa',
enforce: 'pre',
configureServer(vite) {
// TODO: this middleware is specific to the "mon-entreprise" app and
// shouldn't be in the "multipleSPA" plugin
vite.middlewares.use(
'/simulateur-iframe-integration.js',
serveStatic(new URL('./dist', import.meta.url).pathname, {
index: 'simulateur-iframe-integration.js',
})
)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
vite.middlewares.use(async (req, res, next) => {
const url = req.originalUrl?.replace(/^\/%2F/, '/')
const firstLevelDir = url?.slice(1).split('/')[0]
if (url && /\?.*html-proxy/.test(url)) {
return next()
}
if (url && ['/', '/index.html'].includes(url)) {
res.writeHead(302, { Location: '/' + options.defaultSite }).end()
}
// this condition is for the start:netlify script to match /mon-entreprise or /infrance
else if (
firstLevelDir &&
url &&
Object.keys(options.sites)
.map((site) => `/${site}.html`)
.includes(url)
) {
const siteName = firstLevelDir.replace('.html', '')
const content = await vite.transformIndexHtml(
'/' + siteName,
await fillTemplate(siteName)
)
res.end(content)
} else if (
firstLevelDir &&
Object.keys(options.sites).some((name) => firstLevelDir === name)
) {
const siteName = firstLevelDir
const content = await vite.transformIndexHtml(
url,
await fillTemplate(siteName)
)
res.end(content)
} else {
next()
}
})
},
config(config, { command }) {
if (command === 'build' && !config.build?.ssr) {
config.build = {
...config.build,
rollupOptions: {
...config.build?.rollupOptions,
input: Object.fromEntries(
Object.keys(options.sites).map((name) => [
name,
`virtual:${name}.html`,
])
),
},
}
}
},
resolveId(id) {
const pathname = id.split('/').slice(-1)[0]
if (pathname?.startsWith('virtual:')) {
return pathname.replace('virtual:', '')
}
return null
},
async load(id) {
if (
Object.keys(options.sites).some((name) => id.endsWith(name + '.html'))
) {
return await fillTemplate(id.replace(/\.html$/, ''))
}
},
}
}
/**
* Git branch name
*/
export const getBranch = (mode: string) => {
let branch: string | undefined = env(mode)
.VITE_GITHUB_REF?.split('/')
?.slice(-1)?.[0]
if (branch === 'merge') {
branch = env(mode).VITE_GITHUB_HEAD_REF
}
return branch ?? ''
}
/**
* We use this function to hide some features in production while keeping them
* in feature-branches. In case we do A/B testing with several branches served
@ -268,10 +156,22 @@ export const getBranch = (mode: string) => {
* be build in production mode (with the NODE_ENV) but we may still want to show
* or hide some features.
*/
export const isProductionBranch = (mode: string) => {
const isProductionBranch = (mode: string) => {
return ['master', 'next'].includes(getBranch(mode))
}
const getBranch = (mode: string) => {
let branch: string | undefined = env(mode)
.VITE_GITHUB_REF?.split('/')
?.slice(-1)?.[0]
if (branch === 'merge') {
branch = env(mode).VITE_GITHUB_HEAD_REF
}
return branch ?? ''
}
const cleanAutomaticTag = (data: ValidYamlType): ValidYamlType => {
if (typeof data === 'string' && data.startsWith('[automatic] ')) {
return data.replace('[automatic] ', '')