Files in dist
When you build your new package, you'll find various files dist
. Here we'll try to explain what they are and why you need them.
Each file targets a different use case. You'll find several of the files referenced in package.json
, which tells other tools which file to use. If you're familiar with those fields in package.json
then you should be able to get some sense of what those files are for.
A project will look something like this, using the name my-lib
as an example:
📁 packages
📁 my-lib
📁 dist
📁 src
📄 package.json
📄 vite.config.mts
Inside dist
you should see files like these:
📁 dist
📄 my-lib.cjs
📄 my-lib.css
📄 my-lib.d.ts
📄 my-lib.esm.dev.mjs
📄 my-lib.esm-browser.prod.js
📄 my-lib.esm-bundler.prod.mjs
📄 my-lib.global.dev.js
📄 my-lib.global.prod.js
📄 my-lib.prod.css
We'll go into detail about these files later, but in brief:
- Files with a
.css
ending are CSS. - The file ending
.d.ts
contains the TypeScript types. - The other files (with
.cjs
,.js
and.mjs
endings) are JavaScript files. - Files with
dev
in their names are intended to be used during development. - Files with
prod
in their names are intended to be used in production. esm
indicates that a file is an ES module (i.e. it supportsimport
/export
)..cjs
indicates a CommonJS file (i.e. it supportsrequire()
).global
indicates that a file exposes the library via a global variable.
Depending on the specifics of your project, it's possible that you don't need all of these files, or maybe you need more alternatives for other use cases. Either way, you can change which files are built by modifying vite.config.mts
and package.json
.
Building .vue
files
Libraries that use .vue
files face specific challenges. The default project configuration assumes that you do want to use .vue
files in your library source code.
There are two ways to handle .vue
files in libraries:
- Include the raw
.vue
files in the built library. - Compile the
.vue
files down to.js
files.
The key difference is when the files are compiled. The first approach forces the consuming application to compile them, whereas the second approach performs the compilation as part of the library's build.
There are pros and cons with either approach, but the project scaffolded with this tool uses the second approach.
Development vs production
For applications, the distinction between development and production is usually clear.
For libraries, there are two build processes to consider: building the library itself and building the application. The consuming application might also not have a build process, e.g. for sites using libraries from a CDN.
Rather than a clear split between development and production, we instead have three stages: developing the library, developing the consuming application, and finally production. The first stage, developing the library itself, doesn't impact what we publish to npm, so we can ignore that here.
The production stage is also relatively straightforward.
The stage that poses the most challenges is developing the consuming application. We need to build our library in such a way that it'll work well during that stage.
When building .vue
files, the SFC compiler needs to know whether to generate a development or production build. There are several differences between development and production builds, but for now we'll just focus on three of them:
- A development build is fully compatible with Vue Devtools, whereas a production build only has limited Devtools support. Externally available properties, like
$props
and$attrs
, will be shown in the Devtools in either build, but component state inside<script setup>
will only be exposed in development builds. - Type-based prop definitions are compiled to a helpful runtime equivalent in a development build, whereas a production build will aim for minimal code.
- A development build includes the full (absolute) file path of each
.vue
file in the built code, under a property called__file
.
You can see the first two differences for yourself in the Vue Playground at https://play.vuejs.org/. The compiled component is visible in the JS tab, and you can toggle between DEV
and PROD
using the button at the top of the page. The difference in the __file
property isn't apparent without the full Vite build-chain.
Having two different compiled forms means we need to commit to which one we want when we build the library.
Including the full path in __file
is a headache. The build output of a library should be consistent between users, so it shouldn't depend on where the project is stored on the local filesystem. The full path can also potentially include sensitive information, such as usernames or company names, risking doxxing the library's author.
Ideally we'd want a third mode here, aimed at building development builds of libraries. Devtools support and type-based prop compilation should work like a development build, but features like the __file
property should behave like production, as they aren't relevant to developing the downstream application.
That doesn't exist, but we can get close by using a production build with prodDevtools: true
. That enables Devtools support, but sadly we won't get the improved runtime props definition.
To accommodate this, we need to generate dev
and prod
builds, even when the downstream application is using a bundler. The dev
builds still use production mode, but with prodDevtools: true
. The package.json
then tells the downstream bundler which one to use in each case.
SSR
The SFC compiler can also generate special SSR builds for .vue
files, allowing them to skip VNodes on the server and just generate HTML strings directly. You can see this in the Vue Playground at https://play.vuejs.org/, in the SSR tab.
Currently, the project created using this scaffolding tool does not generate special SSR builds. It should still work with SSR, but it'll use the slower VNode-based approach.
The files
<name>.cjs
This is a CommonJS build. It is intended to be used in Node applications that use require()
.
Some features of this build:
- The file is not minified, as it isn't used in the browser.
- The global
__DEV__
flag will depend on the runtime value ofprocess.env.NODE_ENV
. This is only relevant if you're using it in your library code. - SFCs will be compiled in production mode, without
prodDevtools: true
.
There is an argument for having separate dev
and prod
builds, but the generated project won't currently be configured that way.
<name>.css
and <name>.prod.css
These are the built CSS files for the project. The <style>
sections of any components will end up in these files.
The prod
file is minified, but they should otherwise be the same. If the consuming application is using a bundler then it should minify the CSS itself, so the minified build is only really relevant to applications that don't have a build step.
There currently isn't any support in the generated project for splitting the CSS file into smaller chunks.
Consuming applications will need to include the CSS file manually. With a bundler that would usually mean importing it, without a bundler it'd usually be a <link rel="stylesheet">
in the HTML file.
<name>.d.ts
This file contains the TypeScript types. This should include type information for anything exported by the package, including any components.
Tooling in the consuming application should pick this up automatically, as it's referenced from package.json
.
<name>.esm.dev.mjs
This file exposes the library as an ES module. It is intended to be used during development, either with a bundler or directly in the browser via <script type="module">
and import maps (see the section about <name>.esm-browser.prod.js
for more information about direct browser usage).
Some features of this build:
- The file is not minified, to make debugging easier.
- The global
__DEV__
flag will be set totrue
. This is only relevant if you're using it in your library code. - SFCs will be compiled in production mode, but with
prodDevtools: true
.
In production, you would use either <name>.esm-browser.prod.js
or <name>.esm-bundler.prod.mjs
instead.
This build is suitable for use in the Vue Playground:
If your library needs to differentiate between bundlers and direct browser usage during development then you may need to adjust the build to generate more files.
<name>.esm-browser.prod.js
This file is an ES module build intended to be used directly in the browser, not via a bundler. For example:
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js",
"@skirtle/example-vue-lib": "https://unpkg.com/@skirtle/example-vue-lib/dist/example-vue-lib.esm-browser.prod.js"
}
}
</script>
<script type="module">
import { ExampleComponent } from '@skirtle/example-vue-lib'
// ...
</script>
It's a production build, so in real code the versions should be pinned.
An import map is required. While browsers do support importing directly from a URL, that wouldn't be able to resolve imports from vue
inside the library. So, for example, this wouldn't work:
<script type="module">
// This won't work.
// It won't be able to resolve 'vue' inside the imported file.
import { ExampleComponent } from 'https://unpkg.com/@skirtle/example-vue-lib/dist/example-vue-lib.esm-browser.prod.js'
// ...
</script>
This build is suitable for use in the Vue Playground:
In particular, note the configuration in the Import Map
tab.
Some features of this build:
- The file is minified, reducing its size. Note that Vite doesn't fully minify
esm
builds for libraries. - The global
__DEV__
flag will be set tofalse
and dead code removed. This is only relevant if you're using it in your code. - SFCs will be compiled in production mode, without
prodDevtools: true
.
During development, you'd normally use <name>.esm.dev.mjs
instead.
<name>.esm-bundler.prod.mjs
This is a production ES module build, intended to be used by a bundler.
Some features of this build:
- The file is not minified. The bundler will be expected to handle that.
- The global
__DEV__
variable will depend on the bundler's value forprocess.env.NODE_ENV
. - SFCs will be compiled in production mode, without
prodDevtools: true
.
From a bundler's perspective, the only significant difference between this build and the <name>.esm.dev.mjs
build is that .vue
files are built without prodDevtools: true
. If you aren't using .vue
files in your library code then this file can be used in both development and production. That would be similar to libraries like Vue core, Vue Router and Pinia, which just have an esm-bundler
build, with no distinction between dev
and prod
. In that scenario, <name>.esm.dev.mjs
is only used in the browser, so it could be renamed to something like <name>.esm-browser.dev.js
.
<name>.global.dev.js
and <name>.global.prod.js
Global builds can be used without build tools, just by including a <script>
tag in the HTML page. They are built using the IIFE format.
The library is exposed using a global variable. For example, the library @skirtle/example-vue-lib
can be used something like this:
<body>
<div id="app">
<example-component></example-component>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/@skirtle/example-vue-lib/dist/example-vue-lib.global.dev.js"></script>
<script>
Vue.createApp({
components: {
ExampleComponent: ExampleVueLib.ExampleComponent
}
}).mount('#app')
</script>
</body>
You can see a complete, running example in this JSFiddle.
The global build of Vue creates a global variable called Vue
, exposing createApp
. The global build of @skirtle/example-vue-lib
creates a global variable called ExampleVueLib
, allowing us to access ExampleComponent
.
In production applications, the prod
builds of both vue
and @skirtle/example-vue-lib
should be used instead, and exact versions should be pinned in the URL.
The differences between the dev
and prod
builds are:
prod
is minified,dev
isn't.dev
sets__DEV__
totrue
,prod
sets it tofalse
, with any dead code removed.dev
builds useprodDevtools: true
.
build:dev
, build:neutral
, build:prod
The default build for the generated project is split into 3 targets in scripts
, to accommodate the files we need to generate. If you decide to add or remove more built files then you may also need to add or remove build targets from package.json
, as well as adjusting vite.config.mts
.
A single Vite build can produce multiple files, but those files must share most of their build options and only one file can be created for each format.
There's nothing special about having 3 builds, that's just how many we need to create the combinations we need. We generate 3 esm
files in dist
, so we need at least 3 builds to achieve that.
Roughly speaking:
build:dev
- unminified development builds that can be served to the browser without further build tools:<name>.esm.dev.mjs
and<name>.global.dev.js
.build:neutral
- unminified builds that won't go directly to the browser:<name>.cjs
and<name>.esm-bundler.prod.js
.build:prod
- minified production builds to be served directly to the browser:<name>.esm.prod.js
and<name>.global.prod.js
The TypeScript types only need to be generated once, so they get bolted onto build:neutral
.
Each build will also generate a CSS file, but two of them will be the same and will be given the same name.