Setup
vite-multiple-assets
support vite to run with multiple directory public directory.
Feature
- No copy file when run dev, help with no impact on performance when launching apps with vite
- Support automatically copy files when running build
- Support SSR
Quick setup would be in the vite.config.js
:
- Default with vite's configuration you can only use 1 publicDir in
vite.config.ts
export default defineConfig({
// default is public folder
publicDir:''
})
- With this plugin you can add multiple public folders
Install
npm i -D vite-multiple-assets
Basic Usage
In vite.config.ts
import { type PluginOption } from 'vite'
import { DynamicPublicDirectory } from "vite-multiple-assets";
// same level as project root
const dirAssets = ["public/**", "libs/{\x01,assets}/**", "repo1/{\x01,assets}/**"];
// example
const mimeTypes = {
'.acc':'application/acc'
}
export default defineConfig({
plugins: [
DynamicPublicDirectory( dirAssets, {
ssr: true,
mimeTypes
}) as PluginOption
],
publicDir: false,
})
- With the above configuration will automatically add files in
libs/assets
, repo1/assets
folders as static assets for your project
- It also recommended to set
publicDir
to false
to avoid confusion
- Notice the wildcard
**
, this plugin use glob pattern by default. You could also review fast-glob and micromatch
Astro Support
In astro.config.mjs
import {defineConfig} from 'astro/config';
import {astroMultipleAssets} from "vite-multiple-assets";
const assets = ['public2/**'];
// https://astro.build/config
export default defineConfig({
integrations: [
astroMultipleAssets(assets)
],
});
Example
Options
import type { PluginOption } from "vite";
export default function DynamicPublicDirectory(assets: IAssets, opts: IConfig = {}): PluginOption
import type { Options } from "fast-glob";
export interface IConfigExtend extends Partial<Pick<Options, "ignore" | "dot">> {
dst?: string | FDst;
}
export interface IConfig extends
IConfigExtend,
Partial<Pick<Options, "onlyFiles" | "onlyDirectories" | "cwd" | "markDirectories">>
{
__dst?: string; // NOTE: internal destination from parsing rollup write bundlers nor vite config.
mimeTypes?: IMIME;
ssr?: boolean;
}
assets
export type IObjectAssets = {
input: string;
output: string;
}
var assets: IAssets = [];
export type IAssets = (string | IObjectAssets)[];
List copied assets
by following pattern defined in fast-glob
pattern which is Unix Glob Pattern. There are few things to take:
- always use forward-slash
/
for path, and use backslash to escape character
- do not use single-dot
./**/*.txt
./abc/**/*.txt
for current path, instead straight use the pattern **/*.txt
abc/**/*.txt
- If you are hope certain folder included to be copied, make it in selectable list pattern,
dir1/{\x01,folder_name}/**
. Just ensure there is no folder or file named by first ascii
For example:
export default defineConfig({
plugins: [
DynamicPublicDirectory( [
"public/{\x01, models}/**", // order is important
"public/**", // basic public needs
"../../assets/**" // you could go to upper level
] )
],
publicDir: false,
})
Match and out examples:
`/a/b/c/d/**` + `/a/b/c/d/efg.txt` = `efg.txt`
`/a/b/{\x01,c}/d/**` + `/a/b/c/d/efg.txt` = `c/d/efg.txt`
Custom output assets
Similar to the above configurations, the only difference is that you can add output with your own paths
export default defineConfig({
plugins: [
DynamicPublicDirectory([
{
input: "public/{\x01, models}/**",
output: "/shared/assets/models"
}, // order is important
"public/**", // same {input:public/**, output:""}
"../../assets/**" // you could go to upper level
])
],
publicDir: false,
})
so files in public/models
will be copied to shared/assets/models
opts.cwd
var opts_cwd: string = process.cwd();
Where does the beginning root to traverse all directory and files. If you not define, it would goes using Vite's .root
. If not defined, it would goes using process.cwd()
by default.
opts.cacheOptions
{
"Cache-Control":"max-age=31536000, immutable",
...
}
Note: Working only development
opts.ssr
var opts_ssr: boolean = false;
SSR option accept certain value:
true
: support using Solid-js or framework with SSR
false
(default): support client side, static build
If you need to go up, you could use %2E%2E/file.txt
as alternative of ../file.txt
. Also, you could you %2Fetc/wwwroot
as alternative of /etc/wwwroot
. Just in case you need it.
opts.mimeTypes
var opts_mimeTypes: IMIME = {}
export type IMIME = Record<string, string>;
const internalMimeTypes: IMIME = {
".html": "text/html",
".js": "text/javascript",
".css": "text/css",
".png": "image/png",
".jpg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".json": "application/json",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "font/eot",
".otf": "font/otf",
".wasm": "application/wasm",
".mjs": "text/javascript",
".txt": "text/plain",
".xml": "text/xml",
".wgsl": "text/wgsl",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".webm": "video/webm",
".webp": "image/webp",
".ico": "image/x-icon",
".tiff": "image/tiff",
".gz": "application/gzip",
".zip": "application/zip",
".rar": "application/x-rar-compressed",
".7z": "application/x-7z-compressed"
};
You could define your extended content types using opts.mimeTypes
. This feature could be use when using SSR
mode. There are few hierarchi when choosing the right types:
- get the last extention and compare it to your
opt.mimeTypes
. If not exists,
- get the last extention and compare it to
internalMimeTypes
. If not exists,
- do
mime.lookup
to the entire filename so it will be automatically handled according to the mime-types
library by mime-db
. If not exists,
- just use
.html
content type from opts.mimeTypes
and then internalMimeTypes
. If not exists,
- just lookup
.html
content type from mime-types
opts.ignore
var opts_ignore: string[] = [];
List of pattern to be ignored. If you want to include all files from root and inside public/
but not the public itself, you could do as follow:
export default defineConfig({
plugins: [
DynamicPublicDirectory( [ "public/**", "**" ], {
ignore: ["/public"]
} )
],
publicDir: false,
})
Notice: public/**
and /public
are different.
opts.dst
// NOTE: internal destination from parsing rollup write bundlers nor vite config.
// If opts.dst is defined and is string,
let opts_0__dst: string = opts_dst || writeBundleOptions?.dir || viteConfig?.build.outDir;
var opts_dst: string | FDst = writeBundleOptions?.dir || viteConfig?.build.outDir;
export type FDst = (params: {
// CORE
dst: string; // destination folder
assets: IAssets; // list of assets pattern
// UTILITIES
filepath: string; // absolute path to the source file would be copied
baseFile: string; // internal normalization; most common to use this one
dstFile: string; // NOTE: either absolut or non-slash on beginning path to target copied;
// default destination to the last filename
// MUTABLE
writeBundleOptions?: NormalizedOutputOptions; // modify rollup option
viteConfig: IViteResolvedConfig; // modify vite option
opts: IConfig; // {__dst, ...opts};
// modify internal configuration
__files: IFilesMapper; // NOTE: internal use to modify all compiled files entirely;
// NOTE: user can return false and define its own compilation
}) => string | false | void;
export type IFilesMapper = Partial<Record<string, string>>; // STUB: { baseTransformedFilePath: toAbsolutePath }
Where the files would be copied. In default, it would reuse Rollup output or Vite output. This options allow user to reroutes to different folder, or prorammatically reroutes these copied files and rename it. Programmatically reroutes has 3 return options:
string
: path with filename relative to default destination directory, or absolute path. Rules from assets
also applied.
false
: do not copy file. Can be use to programmatically ignore certain file. Use this if you need complex use-case.
undefined
: copy to default name and destination
You could reroutes to different folder:
import path from "path/posix"; // NOTE: use posix for relative transformation
DynamicPublicDirectory(["public/**"], {
// goto `.cache/` instead of `dist/`
dst: path.join(__dirname, ".cache"),
})
You could also make your own ignore
and filter:
DynamicPublicDirectory(["public/**"], {
dst: ({ dstFile, filepath, }) => filepath.match(/.jpg$/ig) ? false : undefined,
})
If you need to add more files which not depend with this plugin's internal logic; or you need to modify base for SSR, you could modify __files
.
let nonce = false;
DynamicPublicDirectory(["public/**"], {
dst: ({
opts: {__dst, ...opts}, __files, // muttable
dstFile, filepath, baseFile, // useful params to use
}) => {
if (!nonce) {
__files["new-no/existing/file.png"] = "/home/me/Pictures/image.png"
nonce = true;
}
}
})
opts.dot
var opts_dot: boolean = true;
Also search for dotfile (hidden files of linux). Those are in examples .env
, .npmrc
, .git/
, .gitignore/
, etc. For conveince to make everything discoverable, this option is true
by default.
opts.onlyFiles
var opts_onlyFiles: boolean = true;
Search and gather only files (with no directories). For convenience to get lower size and more countable, this option is true
by default. If you want to also copy empty folder, you could set false
to this options. It would be wise if you also set opts.onlyDirectories
to false
.
opts.onlyDirectories
var opts_onlyDirectories: boolean = false;
Search and gather only directories (with no files). Opposite to opts.onlyFies
, this option is false
by default. Assuming, just copying folders is useless.
Enable this options can lead to duplication issue, the folder which contains files inside it also reached, and also all folders inside it. When coping the folder, the same folders may be copied twice or more.
opts.markDirectories
var opts_markDirectories: boolean = true;
Mark all path of directory noticeable by adding the last slash at the end of the path. For conveince, this option is true
by default. Assume this structure:
dirA/
\_ dirAA/
\_ file.txt
dirB/
The list of folder could be:
dirA/
dirA/dirAA/
dirA/file.txt
dirB/
Enforced Behaviors
All opts.*
internally directly passed to fast-glob
, so you could override opts
types and use any fast-glob
available flags. This is not recommended as these options already selected to fit internal logic and this plugin original use-case and ideation. Even though that, there is options that is enforced to satisfy internal logic.
opts.absolute
const opts_absolute: boolean = false;
This option is forced to be false
. All path would return to relative path from cwd
. These is intended due to simpler internal logic, and easier in rejoin path to dst
.