Skip to main content

Overview

Supported Browsers and Features

Shuvi.js supports all modern browsers (Edge, Firefox, Chrome, Safari, Opera, et al) with no required configuration.

Polyfills

Shuvi.js inject widely used polyfills, including:

If any of your dependencies includes these polyfills, they’ll be eliminated automatically from the production build to avoid duplication.

In addition, to reduce bundle size, Shuvi.js will only load these polyfills for browsers that require them. The majority of the web traffic globally will not download these polyfills.

client-Side Polyfills

Use node native module belows on client-side, will not throw error when bundled.

JavaScript Language Features

shuvi allows you to use the latest JavaScript features out of the box.

javascript files can be extended with .js, .jsx, .ts, or .tsx;

In addition to ES6 features, also supports:

Typescript Support

shuvi has built-in TypeScript support. shuvi provide types both runtime and plugins.

CSS Support

shuvi allows you to import CSS files from a JavaScript file. This is possible because shuvi extends the concept of import beyond JavaScript.

Adding a Global Stylesheet

To add a stylesheet to your application, import the CSS file within src/app.js.

For example, consider the following stylesheet named styles.css:

body {
font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica',
'Arial', sans-serif;
padding: 20px 20px 60px;
max-width: 680px;
margin: 0 auto;
}

Create a pages/src.js file if not already present. Then, import the styles.css file.

import '../styles.css'
export default (App) => <App />;

These styles (styles.css) will apply to all pages and components in your application. Due to the global nature of stylesheets, and to avoid conflicts, you may only import them inside src/app.js.

In development, expressing stylesheets this way allows your styles to be hot reloaded as you edit them—meaning you can keep application state.

In production, all CSS files will be automatically concatenated into a single minified .css file.

Import styles from node_modules

For global stylesheets, like bootstrap or nprogress, you should import the file inside src/app.js. For example:

// src/app.js
import 'bootstrap/dist/css/bootstrap.css'
export default (App) => <App />;

For importing CSS required by a third party component, you can do so in your component. For example:

// components/ExampleDialog.js
import { useState } from 'react'
import { Dialog } from '@reach/dialog'
import VisuallyHidden from '@reach/visually-hidden'
import '@reach/dialog/styles.css'

function ExampleDialog(props) {
const [showDialog, setShowDialog] = useState(false)
const open = () => setShowDialog(true)
const close = () => setShowDialog(false)

return (
<div>
<button onClick={open}>Open Dialog</button>
<Dialog isOpen={showDialog} onDismiss={close}>
<button className="close-button" onClick={close}>
<VisuallyHidden>Close</VisuallyHidden>
<span aria-hidden>×</span>
</button>
<p>Hello there. I am a dialog</p>
</Dialog>
</div>
)
}

Adding Component-Level CSS

shuvi supports CSS Modules using the [name].module.css file naming convention.

CSS Modules locally scope CSS by automatically creating a unique class name. This allows you to use the same CSS class name in different files without worrying about collisions.

This behavior makes CSS Modules the ideal way to include component-level CSS. CSS Module files can be imported anywhere in your application.

For example, consider a reusable Button component in the components/ folder:

First, create components/Button.module.css with the following content:

/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
.error {
color: white;
background-color: red;
}

Then, create components/Button.js, importing and using the above CSS file:

import styles from './Button.module.css'

export function Button() {
return (
<button
type="button"
// Note how the "error" class is accessed as a property on the imported
// `styles` object.
className={styles.error}
>
Destroy
</button>
)
}

CSS Modules are an optional feature and are only enabled for files with the .module.css extension. Regular <link> stylesheets and global CSS files are still supported.

In production, all CSS Module files will be automatically concatenated into many minified and code-split .css files. These .css files represent hot execution paths in your application, ensuring the minimal amount of CSS is loaded for your application to paint.

Sass Support

shuvi allows you to import Sass using both the .scss and .sass extensions. You can use component-level Sass via CSS Modules and the .module.scss or .module.sass extension.

Sass support has the same benefits and restrictions as the built-in CSS support detailed above.

Note: Sass supports two different syntaxes, each with their own extension. The .scss extension requires you use the SCSS syntax, while the .sass extension requires you use the Indented Syntax ("Sass").

If you're not sure which to choose, start with the .scss extension which is a superset of CSS, and doesn't require you learn the Indented Syntax ("Sass").

Runtime Config

Runtime config is a Store for application storage constants values.

Users can get the Store by call getRuntimeConfig everywhere both client-side and server-side.

import { getRuntimeConfig } from '@shuvi/runtime';

const isServer = typeof window === 'undefined';
const runtimeConfig = getRuntimeConfig();

const getApp = App => () =>
(
<div>
<div id="app">{isServer ? null : runtimeConfig.a}</div>
<App />
</div>
);

export default getApp;

Users could have publicRuntimeConfig and runtimeConfig configs. The keys in the runtimeConfig only available on the server side

Dotenv

shuvi support dotenv by default. Loads environment variables from a .env file into process.env.

shuvi can loaded env files that under application root directory, Special files name (such as development .env or production .env) should include process.env.NODE_ENV, The priority and file name rules as showed below:

const mode = process.env.NODE_ENV;
// Priority top to bottom
const dotenvFiles = [
`.env.${mode}.local`,
`.env.local`,
`.env.${mode}`,
'.env'
];

low priority .env file content will be override by higher priority .env file content, it dependencies process.env.NODE_ENV. default process.env.NODE_ENV is development when shuvi dev mode and production when shuvi build or shuvi serve. recommend split all env variable to .env(common env), .env.development(env for development) and .env.production(env for production).

Global Constant

shuvi inject some global constant to replaces variables in your code with other values or expressions at compile time. examples usage

collect constants form process.env, config.env and some is shuvi defined.

  • process.env: The key should startWith SHUVI_PUBLIC_.

  • config.env: The key should match Regex /^(?:NODE_.+)|^(?:__.+)$/i.

  • shuvi defined

    • 'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production')
    • __BROWSER__: isClientSide?true:false,
    • 'process.env': JSON.stringify('{}') only inject on client-side

Dynamic Import

shuvi supports ES2020 dynamic import() for JavaScript. With it you can import JavaScript modules dynamically and work with them. They also work with SSR.

In the following example, we implement fuzzy search using fuse.js and only load the module dynamically in the browser after the user types in the search input:

You can think of dynamic imports as another way to split your code into manageable chunks.

React components can also be imported using dynamic imports, but in this case we use it in conjunction with dynamic to make sure it works like any other React Component. Check out the sections below for more details on how it works.

Basic usage

In the following example, the module ../components/hello will be dynamically loaded by the page:

import { dynamic } from '@shuvi/runtime';

const Hello = dynamic(() => import('../components/helloComponent'));

export default Hello;

helloComponent will be the default component returned by ../components/helloComponent. It works like a regular React Component, and you can pass props to it as you normally would.

Note: In import('path/to/component'), the path must be explicitly written. It can't be a template string nor a variable. Furthermore the import() has to be inside the dynamic() call for shuvi to be able to match webpack bundles / module ids to the specific dynamic() call and preload them before rendering. dynamic() can't be used inside of React rendering as it needs to be marked in the top level of the module for preloading to work, similar to React.lazy.

With named exports

If the dynamic component is not the default export, you can use a named export too. Consider the module ../components/hello.js which has a named export Hello:

export function Hello() {
return <p>Hello!</p>
}

To dynamically import the Hello component, you can return it from the Promise returned by import(), like so:

import { dynamic } from "@shuvi/runtime";

const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)

function Mod() {
return (
<div>
<Header />
<DynamicComponent />
</div>
)
}

export default Mod

With custom loading component

An optional loading component can be added to render a loading state while the dynamic component is being loaded. For example:

import { dynamic } from "@shuvi/runtime";

const DynamicComponentWithCustomLoading = dynamic(
() => import('../components/hello'),
{ loading: () => <p>LOADING</p> }
)

function Load() {
return (
<div>
<Header />
<DynamicComponentWithCustomLoading />
</div>
)
}

export default Load

With no SSR

You may not always want to include a module on server-side. For example, when the module includes a library that only works in the browser.

import { dynamic } from "@shuvi/runtime";

const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello'),
{ ssr: false }
)

function NoSSR() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
</div>
)
}

export default NoSSR

Dynamic Invalid

If there is already a normal module by import, dynamic will be invalid.

import { dynamic } from "@shuvi/runtime";
import Welcome from "../components/welcome";

const Welcome2 = dynamic(() => import("../components/welcome"));

export default () => (
<div>
<Welcome name="normal" />
<Welcome2 name="dynamic" />
</div>
);

http-proxy

Quick proxy requests by shuvi.config

Serving Static Files

Static Files under public before built

Shuvi.js can serve static files like images, CSS files, or JavaScript files, under the /public folder. Files inside the folder will be treated as static resources which could be accessed by the prefix /.

example:

Access the image public/icon.png by following code.

src/pages/index.js
function HomePage() {
return (
<img
src="/icon.png" alt="icon" width="20" height="30"
/>
)
}
export default HomePage;

Static Files Generated at the Build Time

publicPath serves static files generated after project has built, _shuvi has set by default.

Access the json file cities.json by typing [ip]:[port]/cities.json directly on the browser's address bar.

dist/client/city.json
// a list of cities generated at the built time
{
"key1": "city1",
"key2": "city2",
// ...
}

Change Public Path

Default publicPath is _shuvi, There is a way to override runtime publicPath:

// /src/document
import { IDENTITY_RUNTIME_PUBLICPATH } from '@shuvi/shared/lib/constants';

export function onDocumentProps(documentProps) {
documentProps.headTags.push({
tagName: 'script',
attrs: {
name: 'test',
},
innerHTML: `${IDENTITY_RUNTIME_PUBLICPATH} = "/client-overwrite/"`
});

return documentProps;
}

override code should run ahead of other codes