Skip to content

Instantly share code, notes, and snippets.

@WhiteAbeLincoln
Last active December 23, 2021 20:56
Show Gist options
  • Save WhiteAbeLincoln/f6842dcca3d08fe0aead127fe452a533 to your computer and use it in GitHub Desktop.
Save WhiteAbeLincoln/f6842dcca3d08fe0aead127fe452a533 to your computer and use it in GitHub Desktop.
Deduplicate entry points for vue-cli multi-page build
// @ts-check
const { defineConfig } = require('@vue/cli-service')
const path = require('path')
/** @typedef {NonNullable<import('@vue/cli-service').ProjectOptions['pages']>[string]} Page */
/** @typedef {import('webpack-chain')} ChainConfig */
/**
* Gets the entry points defined for a page.
* @param {Page} page
* @returns {string[]} an array of entry points
*/
function getEntries(page) {
if (Array.isArray(page)) {
return page
}
if (typeof page === 'string') {
return [page]
}
return getEntries(page.entry)
}
/**
* Checks any member of an iterable passes the predicate
* @template A
* @param {Iterable<A>} it
* @param {(v: A) => boolean} pred
* @returns {boolean}
*/
function some(it, pred) {
for (const v of it) {
if (pred(v)) return true
}
return false
}
/**
* Returns a function that can get a unique bundle name given a list of entry files
*/
function getBundleName() {
/** @type {Map<string, string>} */
const cache = new Map()
/**
* A list of entry files. The files are expected to be absolute.
* @param {string[]} entries
* @returns A unique bundle name for the given entry files
*/
return entries => {
const paths = entries.map(p => (p.startsWith(__dirname) ? path.relative(__dirname, p) : p)).sort()
const key = paths.join('\n')
const entry = cache.get(key)
if (entry) {
return entry
}
let value = 'entry-' + paths.map(p => path.basename(p).replace(/\.(m?[tj]sx?|vue)$/, '')).join('-').toLowerCase()
let incr = 0
const initVal = value
while (some(cache.values(), v => v === value)) {
value = `${initVal}-${++incr}`
}
cache.set(key, value)
return value
}
}
/**
* Given a pages config, creates a pair of mappings.
* The first mapping is of the original chunk/page names to the deduplicated chunk names
* The second mapping is of the deduplicated chunk names to the entry files in that chunk.
* @param {Record<string, Page>} pages
* @returns a pair of maps
*/
function createPageMap(pages) {
const getter = getBundleName()
return Object.entries(pages)
.reduce((pair, [key, v]) => {
const {origToDedup, dedupEntries: realEntries} = pair
const entries = getEntries(v).map(p => path.resolve(__dirname, p))
// make a unique name for the bundle based on the paths for the entries
const bundle = getter(entries)
if (!realEntries.has(bundle)) {
realEntries.set(bundle, entries)
}
if (!origToDedup.has(key)) {
origToDedup.set(key, bundle)
}
return pair
}, /** @type {{origToDedup: Map<string, string>, dedupEntries: Map<string, string[]>}} */({ origToDedup: new Map(), dedupEntries: new Map() }))
}
/**
* Takes a map of original chunk/page names to real chunk names,
* a map of the deduplicated entries, and modifies the config
* so that each page loads the deduplicated chunk instead of a
* unique chunk for each page.
* @param {Map<string, string>} origToDedup
* @param {Map<string, string[]>} dedupEntries
* @param {ChainConfig} cfg
*/
function dedupConfigEntries(origToDedup, dedupEntries, cfg) {
// We deduplicate our entry points by gathering a list of the unique
// entries from our pages. We then create a new entry point for each unique entry
for (const [key, entries] of dedupEntries.entries()) {
cfg.entry(key).clear().merge(entries)
}
// next we iterate over the each page's html-webpack plugin
// they should be named, with the format `html-${key}`, where
// key is the page key in the original pages map.
// we replace the entry chunk (by default named after the key),
// with the deduplicated chunk. We also remove the original entry
// point, since it is no longer used.
for (const [key, bundle] of origToDedup.entries()) {
cfg.entryPoints.delete(key)
cfg.plugin(`html-${key}`)
.tap(args => {
/** @type {string[]} */
const chunks = args?.[0]?.chunks
if (chunks) {
const idx = chunks.findIndex(c => c === key)
if (idx >= 0) {
chunks[idx] = bundle
}
}
return args
})
}
}
// use as follows
// extract your pages map from the config
const pages = { /* ... */ }
// create the deduplicated entries
const { origToDedup, dedupEntries } = createPageMap(pages)
// call dedupConfigEntries from your chain webpack hook
module.exports = defineConfig({
pages,
chainWebpack: cfg => { dedupConfigEntries(origToDedup, dedupEntries, cfg) },
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment