npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@unbundle/mvt

v0.11.4

Published

bundle less

Downloads

21

Readme

mvt ⚡

No-bundle Dev Server for Vue 3 Single-File Components.

Getting Started

$ npx create-mvt-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

If using Yarn:

$ yarn create mvt-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev

Browser Support

mvt requires native ES module imports during development. The production build also relies on dynamic imports for code-splitting (which can be polyfilled).

mvt assumes you are targeting modern browsers and therefore does not perform any compatibility-oriented code transforms by default. Technically, you can add autoprefixer yourself using a PostCSS config file, or add necessary polyfills and post-processing steps to make your code work in legacy browsers - however that is not mvt's concern by design.

Features

mvt tries to mirror the default configuration in vue-cli as much as possible. If you've used vue-cli or other webpack-based boilerplates before, you should feel right at home. That said, do expect things to be different here and there.

Bare Module Resolving

Native ES imports doesn't support bare module imports like

import { createApp } from 'vue'

The above will throw an error by default. mvt detects such bare module imports in all served .js files and rewrites them with special paths like /@modules/vue. Under these special paths, mvt performs module resolution to locate the correct files on disk:

  • vue has special handling: you don't need to install it since mvt will serve it by default. But if you want to use a specific version of vue (only supports Vue 3.x), you can install vue locally into node_modules and it will be preferred (@vue/compiler-sfc of the same version will also need to be installed).
  • If a web_modules directory (generated by Snowpack) is present, we will try to locate it.
  • Finally we will try resolving the module from node_modules, using the package's module entry if available.

Hot Module Replacement

  • *.vue files come with HMR out of the box.

  • For *.js files, a simple HMR API is provided:

    import { foo } from './foo.js'
    import { hot } from '@hmr'
    foo()
    // this code will be stripped out when building
    if (__DEV__) {
      hot.accept('./foo.js', (newFoo) => {
        // the callback receives the updated './foo.js' module
        newFoo.foo()
      })
      // Can also accept an array of dep modules:
      hot.accept(['./foo.js', './bar.js'], ([newFooModule, newBarModule]) => {
        // the callback receives the updated mdoules in an Array
      })
    }

    Modules can also be self-accepting:

    import { hot } from '@hmr'
    export const count = 1
    // this code will be stripped out when building
    if (__DEV__) {
      hot.accept((newModule) => {
        console.log('updated: count is now ', newModule.count)
      })
    }

    Note that mvt's HMR does not actually swap the originally imported module: if an accepting module re-exports imports from a dep, then it is responsible for updating those re-exports (and these exports must be using let). In addition, importers up the chain from the accepting module will not be notified of the change.

    This simplified HMR implementation is sufficient for most dev use cases, while allowing us to skip the expensive work of generating proxy modules.

TypeScript

Starting with v0.11, mvt supports <script lang="ts"> in *.vue files, and importing .ts files out of the box. Note that mvt does NOT perform type checking - it assumes type checking is taken care of by your IDE and build process (you can run tsc --noEmit in the build script). With that in mind, mvt uses esbuild to transpile TypeScript into JavaScript which is about 20~30x faster than vanilla tsc.

CSS / JSON Importing

You can directly import .css and .json files from JavaScript (including <script> tags of *.vue files, of course).

  • .json files export their content as an object that is the default export.

  • .css files do not export anything unless it ends with .module.css (See CSS Modules below). Importing them leads to the side effect of them being injected to the page during dev, and being included in the final style.css of the production build.

Both CSS and JSON imports also support Hot Module Replacement.

Asset URL Handling

You can reference static assets in your *.vue templates, styles and plain .css files either using absolute public paths (based on project root) or relative paths (based on your file system). The latter is similar to the behavior you are used to if you have used vue-cli or webpack's file-loader.

There is no conventional public directory. All referenced assets, including those using absolute paths, will be copied to the dist folder with a hashed file name in the production build. Never-referenced assets will not be copied.

Similar to vue-cli, image assets smaller than 4kb will be base64 inlined.

CSS Modules

Note that you do not need to configure PostCSS if you want to use CSS Modules: it works out of the box. Inside *.vue components you can use <style module>, and for plain .css files, you need to name CSS modules files as *.module.css which allows you to import the naming hash from it.

CSS Pre-Processors

Because mvt targets modern browsers only, it is recommended to use native CSS variables with PostCSS plugins that implement CSSWG drafts (e.g. postcss-nesting) and author plain, future-standards-compliant CSS. That said, if you insist on using a CSS pre-processor, you can install the corresponding pre-processor and just use it:

yarn add -D sass
<style lang="scss">
/* use scss */
</style>

Note importing CSS / preprocessor files from .js files, and HMR from imported pre-processor files are currently not supported, but can be in the future.

JSX

.jsx and .tsx files are also supported out of the box. JSX transpilation is also handled via esbuild.

Because React doesn't ship ES module builds, you either need to use es-react, or pre-bundle React into a ES module with Snowpack. Easiest way to get it running is:

import { React, ReactDOM } from 'https://unpkg.com/es-react'
ReactDOM.render(<h1>Hello, what!</h1>, document.getElementById('app'))

JSX can also be customized via --jsx-factory and --jsx-fragment flags from the CLI or jsx: { factory, fragment } fro the API. For example, to use Preact with mvt:

Notes on JSX Support

  • Vue 3's JSX transform is still WIP, so mvt's JSX support currently only targets React/Preact.

  • React doesn't ship ES module builds, so it must be pre-bundled with Snowpack to work.

  • There is no out-of-the-box HMR when using non-Vue frameworks, but userland HMR support is technically via the server API.

Production Build

mvt does utilize bundling for production builds, because native ES module imports result in waterfall network requests that are simply too punishing for page load time in production.

You can run mvt build to bundle the app.

  • mvt build --root dir: build files in the target directory instead of current working directory.

  • mvt build --cdn: import vue from a CDN link in the built js. This will make the build faster, but overall the page payload will be larger because therer will be no tree-shaking for Vue APIs.

Internally, we use a highly opinionated Rollup config to generate the build. The build is configurable by passing on most options to Rollup - and most non-rollup string/boolean options have mapping flags in the CLI (see build/index.ts for full details).

API

Dev Server

You can customize the server using the API. The server can accept plugins which have access to the internal Koa app instance. You can then add custom Koa middlewares to add pre-processor support:

const { createServer } = require('@unbundle/mvt')
const myPlugin = ({
  root, // project root directory, absolute path
  app, // Koa app instance
  server, // raw http server instance
  watcher // chokidar file watcher instance
}) => {
  app.use(async (ctx, next) => {
    // You can do pre-processing here - this will be the raw incoming requests
    // before mvt touches it.
    if (ctx.path.endsWith('.scss')) {
      // Note vue <style lang="xxx"> are supported by
      // default as long as the corresponding pre-processor is installed, so this
      // only applies to <link ref="stylesheet" href="*.scss"> or js imports like
      // `import '*.scss'`.
      console.log('pre processing: ', ctx.url)
      ctx.type = 'css'
      ctx.body = 'body { border: 1px solid red }'
    }
    // ...wait for mvt to do built-in transforms
    await next()
    // Post processing before the content is served. Note this includes parts
    // compiled from `*.vue` files, where <template> and <script> are served as
    // `application/javascript` and <style> are served as `text/css`.
    if (ctx.response.is('js')) {
      console.log('post processing: ', ctx.url)
      console.log(ctx.body) // can be string or Readable stream
    }
  })
}
createServer({
  plugins: [myPlugin]
}).listen(3000)

Build

Check out the full options interface in build/index.ts.

const { build } = require('@unbundle/mvt')
;(async () => {
  // All options are optional.
  // check out `src/node/build.ts` for full options interface.
  const result = await build({
    rollupInputOptions: {
      // https://rollupjs.org/guide/en/#big-list-of-options
    },
    rollupOutputOptions: {
      // https://rollupjs.org/guide/en/#big-list-of-options
    },
    rollupPluginVueOptions: {
      // https://github.com/vuejs/rollup-plugin-vue/tree/next#options
    },
    root: process.cwd(),
    cdn: false,
    write: true,
    minify: true,
    silent: false
  })
})()

How and Why

How is This Different from vue-cli or other Bundler-based Solutions?

The primary difference is that for mvt there is no bundling during development. The ES Import syntax in your source code is served directly to the browser, and the browser parses them via native <script module> support, making HTTP requests for each import. The dev server intercepts the requests and performs code transforms if necessary. For example, an import to a *.vue file is compiled on the fly right before it's sent back to the browser.

There are a few advantages of this approach:

  • Since there is no bundling work to be done, the server cold start is extremely fast.

  • Code is compiled on demand, so only code actually imported on the current screen is compiled. You don't have to wait until your entire app to be bundled to start developing. This can be a huge difference in apps with dozens of screens.

  • Hot module replacement (HMR) performance is decoupled from the total number of modules. This makes HMR consistently fast no matter how big your app is.

Full page reload could be slightly slower than a bundler-based setup, since native ES imports result in network waterfalls with deep import chains. However since this is local development, the difference should be trivial compared to actual compilation time. (There is no compile cost on page reload since already compiled files are cached in memory.)

Finally, because compilation is still done in Node, it can technically support any code transforms a bundler can, and nothing prevents you from eventually bundling the code for production. In fact, mvt provides a mvt build command to do exactly that so the app doesn't suffer from network waterfall in production.

How is This Different from es-dev-server?

es-dev-server is a great project and we did take some inspiration from it when refactoring mvt in the early stages. That said, here is why mvt is different from es-dev-server and why we didn't just implement mvt as a middleware for es-dev-server:

  • mvt supports Hot Module Replacement, which surgically updates the updated module without reloading the page. This is a fundamental difference in terms of development experience. es-dev-server internals is a bit too opaque to get this working nicely via a middleware.

  • mvt aims to be a single tool that integrates both the dev and the build process. You can use mvt to both serve and bundle the same source code, with zero configuration.

  • mvt is more opinionated on how certain types of imports are handled, e.g. .css and static assets. The handling is similar to vue-cli for obvious reasons.

TODOs

  • Config file support
    • Define config options
    • Define plugin format

License

MIT