Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into docs
Browse files Browse the repository at this point in the history
  • Loading branch information
juzser committed Apr 1, 2024
2 parents 596a58f + 6b47aa6 commit 9dbeac8
Show file tree
Hide file tree
Showing 28 changed files with 489 additions and 121 deletions.
89 changes: 89 additions & 0 deletions build/configs/append-component-css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { existsSync } from 'fs'
import { extname, dirname, basename, relative, resolve } from 'path'
import { createDistTransformPlugin } from './create-dist-transform'

const parsePath = (path: string) => {
const ext = extname(path).replace('.', '')

return {
ext,
dir: dirname(path),
name: basename(path, `.${ext}`),
}
}

/**
* Checks if file is Vuestic component template source
* If file is script source, but there is not template then add css to script.
* Component usually have script which is stored in file with name like Va[ComponentName].vue_vue_type_script_lang
*
* @notice Component can have render function without template block. It also can have only template without script block.
*/
const isComponent = (filename: string) => {
// [ComponentName].vue_vue_type_script_lang.mjs
// [ComponentName].vue_vue_type_script_setup_true_lang.mjs
const isScriptFile = /\w*.vue_vue_type_script\w*_lang.m?js$/.test(filename)
if (isScriptFile) {
return true
}

// Va[ComponentName].mjs
const isTemplateFile = /\w*\.m?js$/.test(filename)

// Va[ComponentName].vue_vue_type_script_lang.mjs
const scriptFilePath = filename.replace(/\.(mjs|js)/, '.vue_vue_type_script_lang.mjs')
const scriptSetupFilePath = filename.replace(/\.(mjs|js)/, '.vue_vue_type_script_setup_true_lang.mjs')

const haveScript = existsSync(scriptFilePath) || existsSync(scriptSetupFilePath)

if (isTemplateFile && !haveScript) {
return true
}

return false
}

const extractVuesticComponentName = (filename: string) => {
return filename.match(/(\w*)/)?.[1]
}

const SOURCE_MAP_COMMENT_FRAGMENT = '//# sourceMappingURL='

const appendBeforeSourceMapComment = (content: string, append: string): string => {
return content.replace(SOURCE_MAP_COMMENT_FRAGMENT, `${append}\n${SOURCE_MAP_COMMENT_FRAGMENT}`)
}

export const appendComponentCss = createDistTransformPlugin({
name: 'vuestic:append-component-css',

dir: (outDir) => `${outDir}/src/components`,

transform (componentContent, path) {
if (!isComponent(path)) { return }

const { name, dir } = parsePath(path)

const componentName = extractVuesticComponentName(name)

if (!componentName) {
throw new Error(`Can't extract component name from ${name}`)
}

const distPath = resolve(this.outDir, '..', '..')
const relativeDistPath = relative(dir, distPath)
const relativeFilePath = relativeDistPath + '/' + componentName.replace(/-.*$/, '') + '.css'

// There are few cases how vite can store css files (depends on vite version, but we handle both for now):
// CSS stored in dist folder (root)
if (existsSync(resolve(dir, relativeFilePath))) {
return appendBeforeSourceMapComment(componentContent, `\nimport '${relativeFilePath}';`)
}

// CSS stored in component folder
const cssFilePath = `${dir}/${componentName}.css`

if (existsSync(cssFilePath)) {
return appendBeforeSourceMapComment(componentContent, `\nimport './${componentName}.css';`)
}
},
})
142 changes: 142 additions & 0 deletions build/configs/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Inspired by VuesticUI build config
* https://github.com/epicmaxco/vuestic-ui/blob/develop/packages/ui/build/common-config.ts
*/
import { readFileSync, lstatSync, readdirSync } from 'fs'
import vue from '@vitejs/plugin-vue'
import { UserConfig } from 'vite'
import { resolve as resolver } from 'path'
import { chunkSplitPlugin } from 'vite-plugin-chunk-split'
import { removeSideEffectedChunks } from './remove-side-effect-chunks'
import { fixVueGenericComponentFileNames } from './fix-generic-file-names'
import { appendComponentCss } from './append-component-css'
import { generateScopedName } from '../namespaced-classname.js';
import svgLoader from 'vite-svg-loader';
import { replaceCodePlugin } from 'vite-plugin-replace';
import dts from 'vite-plugin-dts';

export const defineViteConfig = <T extends UserConfig>(p: T): UserConfig & T => p

import type { RollupOptions } from 'vite/node_modules/rollup';

const packageJSON = JSON.parse(readFileSync(resolver(process.cwd(), './package.json')).toString())
const dependencies = [...Object.keys(packageJSON.dependencies), ...Object.keys(packageJSON.peerDependencies)]

export type BuildFormat = 'iife' | 'es' | 'cjs' | 'esm-node'

export const readDirRecursive = (path: string): string[] => {
return readdirSync(path)
.reduce<string[]>((acc, entry) => {
const p = `${path}/${entry}`

if (lstatSync(p).isDirectory()) {
return [...acc, ...readDirRecursive(p)]
}

return [...acc, p]
}, [])
}

export const resolve = {
alias: {
'@icons': resolver(process.cwd(), 'node_modules/@shopify/polaris-icons/dist/svg'),
'@polaris': resolver(process.cwd(), './polaris/polaris-react/src'),
'@': resolver(process.cwd(), './src'),
'~': resolver(process.cwd(), './node_modules'),
},
dedupe: ['vue'],
}

const rollupOutputOptions = (ext: string): RollupOptions['output'] => ({
entryFileNames: `[name].${ext}`,
chunkFileNames: `[name].${ext}`,
assetFileNames: '[name].[ext]',
})

const rollupMjsBuildOptions: RollupOptions = {
input: resolver(process.cwd(), 'src/polaris-vue.ts'),

output: {
sourcemap: true,
dir: 'dist/esm-node',
format: 'esm',
...rollupOutputOptions('mjs'),
},
}

const libBuildOptions = (format: 'iife' | 'es' | 'cjs') => ({
entry: resolver(process.cwd(), 'src/polaris-vue.ts'),
fileName: () => 'polaris-vue.js',
formats: [format],

// only for iife/umd
name: 'PolarisVue',
})

export default function createViteConfig (format: BuildFormat) {
const isEsm = ['es', 'esm-node'].includes(format)
const isNode = format === 'esm-node'

const config = defineViteConfig({
resolve,

css: {
preprocessorOptions: {
scss: {
quietDeps: true, // Silent the deprecation warning
},
},
modules: {
generateScopedName,
},
},

build: {
outDir: `dist/${isEsm ? format : ''}`,
cssCodeSplit: false,
sourcemap: true,
minify: isEsm ? false : 'esbuild',
lib: libBuildOptions(isNode ? 'es' : format),
},

plugins: [
vue({
isProduction: true,
exclude: [/\.md$/, /\.spec\.ts$/, /\.spec\.disabled$/],
}),
svgLoader(),
replaceCodePlugin({
replacements: [
{
from: '%POLARIS_VERSION%',
to: packageJSON.polaris_version,
},
],
}),
dts({
rollupTypes: true,
outDir: 'dist/types',
}),
],
})

// https://github.com/sanyuan0704/vite-plugin-chunk-split
isEsm && config.plugins.push(chunkSplitPlugin({ strategy: 'unbundle' }))
// isEsm && !isNode && config.plugins.push(appendComponentCss())
isEsm && config.plugins.push(removeSideEffectedChunks())
isEsm && config.plugins.push(fixVueGenericComponentFileNames)

if (isNode) {
config.build.rollupOptions = {
external: [...dependencies, 'vue'],
...rollupMjsBuildOptions,
};
} else {
config.build.rollupOptions = {
external: [...dependencies, 'vue'],
output: rollupOutputOptions('js'),
}
}

return config
}
43 changes: 43 additions & 0 deletions build/configs/create-dist-transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Plugin } from 'vite'
import { readdir, readFile, writeFile, lstat } from 'fs/promises'

type Nothing = null | undefined | void
type TransformFnResult = string | Nothing
type TransformFn = (this: { outDir: string }, content: string, path: string) => TransformFnResult | Promise<TransformFnResult>

export const createDistTransformPlugin = (options: {
name: string,
dir?: (outDir: string) => string,
transform: TransformFn,
}) => {
let outDir = ''

const processFiles = async (dir: string) => {
return (await readdir(dir))
.map(async (entryName) => {
const currentPath = `${dir}/${entryName}`

if ((await lstat(currentPath)).isDirectory()) {
return processFiles(currentPath)
}

const content = await readFile(currentPath, 'utf8')

const result = await options.transform.call({ outDir }, content, currentPath)

if (!result) { return }

await writeFile(currentPath, result)
})
}

return (): Plugin => ({
name: 'vuestic:append-component-css',
configResolved: (config) => {
outDir = options.dir?.(config.build.outDir) || config.build.outDir
},
closeBundle: async () => {
processFiles(outDir)
},
})
}
56 changes: 56 additions & 0 deletions build/configs/fix-generic-file-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Plugin } from 'vite';

const defineVitePlugin = <T extends Plugin>(p: T): Plugin & T => p;

/**
* When vue compiles file, it encode generic into file name
*
* @example
* AppProvider.vue_vue_type_script_generic_Item%20extends%20Record%3Cstring%2C%20any%3E_setup_true_lang
*
* This might be helpful for compiler, but it makes file names unreadable and some bundlers may not allow encoded characters in file names.
* This plugin replaces encoded characters in file names and source maps with underscores.
*/
const GENERIC_NAME_REGEX = /.vue_vue_type_script_generic_.*_setup_true_lang/gm
const URL_ENCODED_REGEX = /%([0-9]|[A-F]){2}/gm

const replaceEncodedCharacters = (match: string) => match
.replace(URL_ENCODED_REGEX, '_') // Replace any encoded character with underscore
.replace(/_{2,}/g, '_') // Replace multiple underscores with single underscore

export const fixVueGenericComponentFileNames = defineVitePlugin({
name: 'polaris-vue:fix-vue-generic-component-file-names',

generateBundle (options, bundle) {
Object.keys(bundle).forEach(fileName => {
console.log(fileName);
const file = bundle[fileName]

// Replace encoded characters in generic names in source maps
if (file.type === 'asset' && file.fileName.endsWith('.map')) {
if (typeof file.source === 'string') {
file.source = file.source
.replace(GENERIC_NAME_REGEX, replaceEncodedCharacters)
}
}

// Replace encoded characters in generic names in code
if (file.type === 'chunk') {
file.code = file.code
.replace(GENERIC_NAME_REGEX, replaceEncodedCharacters)
}

// Update file name if it has encoded characters
if (GENERIC_NAME_REGEX.test(fileName)) {
const newFileName = replaceEncodedCharacters(fileName)

bundle[newFileName] = {
...bundle[fileName],
fileName: newFileName,
}

delete bundle[fileName]
}
})
},
})
15 changes: 15 additions & 0 deletions build/configs/remove-side-effect-chunks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createDistTransformPlugin } from './create-dist-transform';

/**
* Vite think that some chunks contain side effects,
* so it keep them in bundle and imports with `import "..."`.
* It simply removes all imports from chunks that import side effected chunks
* after build is done.
*/
export const removeSideEffectedChunks = createDistTransformPlugin({
name: 'polaris-vue:remove-side-effected-chunks',

transform: (content) => {
return content.replace(/import ".*";(\n)*/gm, '')
},
})
8 changes: 8 additions & 0 deletions build/configs/vite.cjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import createViteConfig from './common'

export default () => {
return defineConfig({
...createViteConfig('cjs'),
})
}
6 changes: 6 additions & 0 deletions build/configs/vite.es.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import createViteConfig from './common'

export default () => defineConfig({
...createViteConfig('es'),
})
2 changes: 0 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,6 @@ export default defineConfig({
// @ts-ignore
'@polaris': fileURLToPath(new URL('../../polaris/polaris-react/src', import.meta.url)),
// @ts-ignore
'@tokens': fileURLToPath(new URL('../../polaris/polaris-tokens/src', import.meta.url)),
// @ts-ignore
'@': fileURLToPath(new URL('../../src', import.meta.url)),
// @ts-ignore
'~': fileURLToPath(new URL('../../node_modules', import.meta.url)),
Expand Down
Loading

0 comments on commit 9dbeac8

Please sign in to comment.