The modern asset pipeline for Sails.js, powered by Rsbuild.
Shipwright replaces the legacy Grunt-based asset pipeline with a fast, modern bundler that supports TypeScript, ES modules, LESS/SASS, and Hot Module Replacement out of the box.
| Feature | Grunt (legacy) | Shipwright |
|---|---|---|
| Build speed | ~16s | ~1.4s |
| JS bundle size | 3.0MB | 229KB |
| CSS bundle size | 733KB | 551KB |
| Hot Module Replacement | No | Yes |
| TypeScript | No | Yes |
| ES Modules | No | Yes |
| Tree Shaking | No | Yes |
Benchmarks from fleetdm.com migration (fleetdm/fleet#38079)
npm install sails-hook-shipwright --saveDisable the grunt hook in .sailsrc:
{
"hooks": {
"grunt": false
}
}Shipwright works with zero configuration for most apps. Just create your entry point:
assets/
js/
app.js # Auto-detected entry point
styles/
importer.less # Auto-detected styles entry
In your layout, use the shipwright helpers:
<!DOCTYPE html>
<html>
<head>
<%- shipwright.styles() %>
</head>
<body>
<!-- your content -->
<%- shipwright.scripts() %>
</body>
</html>That's it! Shipwright will bundle your JS, compile your styles, and inject the appropriate tags.
Create config/shipwright.js to customize behavior:
module.exports.shipwright = {
js: {
entry: 'assets/js/app.js' // optional, auto-detected by default
},
styles: {
entry: 'assets/styles/app.css' // optional, auto-detected by default
},
build: {
// Rsbuild configuration - see https://rsbuild.dev/config/
}
}Most apps don't need a config file at all - shipwright auto-detects entry points and uses sensible defaults:
- JS inject default:
['dependencies/**/*.js'] - CSS inject default:
['dependencies/**/*.css']
Shipwright auto-detects entry points in this order:
JavaScript:
assets/js/app.jsassets/js/main.jsassets/js/index.js
Styles:
assets/styles/importer.lessassets/styles/importer.scssassets/styles/importer.cssassets/styles/main.lessassets/styles/main.scssassets/styles/main.cssassets/styles/app.lessassets/styles/app.scssassets/styles/app.cssassets/css/app.cssassets/css/main.css
For new apps or apps using import/export:
// assets/js/app.js
import { setupCloud } from './cloud.setup'
import { formatDate } from './utilities/format'
setupCloud()Shipwright detects the single entry point and bundles all imports.
For existing apps that concatenate scripts without ES modules (like Grunt's pipeline.js):
// config/shipwright.js
module.exports.shipwright = {
js: {
entry: [
'js/cloud.setup.js',
'js/components/**/*.js',
'js/utilities/**/*.js',
'js/pages/**/*.js'
]
}
}Files are concatenated in the specified order, preserving the global scope behavior of the legacy pipeline. This is a drop-in replacement for tasks/pipeline.js.
- entry - Files bundled together by Rsbuild (minified, tree-shaken, hashed)
- inject - Files loaded as separate
<script>or<link>tags before the bundle
Use inject for vendor libraries that need to be loaded separately:
module.exports.shipwright = {
js: {
inject: [
'dependencies/sails.io.js',
'dependencies/lodash.js',
'dependencies/jquery.min.js',
'dependencies/vue.js',
'dependencies/**/*.js' // catch remaining dependencies
]
}
}The order is preserved, and duplicates are automatically removed.
Shipwright supports TypeScript out of the box. Just use .ts or .tsx files:
// config/shipwright.js
module.exports.shipwright = {
js: {
entry: 'assets/js/app.ts'
// or with glob patterns:
// entry: ['js/**/*.ts', 'js/**/*.tsx']
}
}No tsconfig.json required for basic usage. Add one if you want strict type checking.
Install the appropriate plugin:
# For LESS
npm install @rsbuild/plugin-less --save-dev
# For SASS/SCSS
npm install @rsbuild/plugin-sass --save-devAdd the plugin to your config:
const { pluginLess } = require('@rsbuild/plugin-less')
module.exports.shipwright = {
build: {
plugins: [pluginLess()]
}
}Shipwright auto-detects your styles entry point (importer.less, main.scss, etc.).
In development, Shipwright provides HMR via Rsbuild's dev server. Changes to your JS and CSS files are instantly reflected in the browser without a full page reload.
HMR is enabled automatically when NODE_ENV !== 'production'.
In production (NODE_ENV=production), Shipwright:
- Minifies JS and CSS
- Adds content hashes for cache busting (
app.a1b2c3d4.js) - Enables tree shaking to remove unused code
- Generates a manifest for asset versioning
.tmp/public/
js/
app.js # development
app.a1b2c3d4.js # production (with hash)
css/
styles.css
styles.b2c3d4e5.css
manifest.json # maps entry names to hashed filenames
dependencies/ # copied from assets/dependencies
images/ # copied from assets/images
...
Shipwright configures these aliases by default:
@→assets/js~→assets
// In your JS files
import utils from '@/utilities/helpers'
import styles from '~/styles/components.css'Pass any Rsbuild configuration via the build key:
const { pluginLess } = require('@rsbuild/plugin-less')
const { pluginReact } = require('@rsbuild/plugin-react')
module.exports.shipwright = {
build: {
plugins: [pluginLess(), pluginReact()],
output: {
// Custom output options
},
performance: {
// Custom performance options
}
}
}See Rsbuild Configuration for all available options.
- Install shipwright and disable grunt:
npm install sails-hook-shipwright --save
npm install @rsbuild/plugin-less --save-dev # if using LESS// .sailsrc
{
"hooks": {
"grunt": false
}
}- Create
config/shipwright.jsbased on yourtasks/pipeline.js:
// If your pipeline.js has:
// var jsFilesToInject = [
// 'dependencies/sails.io.js',
// 'dependencies/lodash.js',
// 'js/cloud.setup.js',
// 'js/**/*.js'
// ]
// Your shipwright.js becomes:
const { pluginLess } = require('@rsbuild/plugin-less')
module.exports.shipwright = {
js: {
entry: [
'js/cloud.setup.js',
'js/components/**/*.js',
'js/utilities/**/*.js',
'js/pages/**/*.js'
],
inject: [
'dependencies/sails.io.js',
'dependencies/lodash.js',
'dependencies/**/*.js'
]
},
build: {
plugins: [pluginLess()]
}
}- Update your layout to use shipwright helpers:
- <!--STYLES-->
- <!--STYLES END-->
+ <%- shipwright.styles() %>
- <!--SCRIPTS-->
- <!--SCRIPTS END-->
+ <%- shipwright.scripts() %>- Remove the
tasks/directory (optional, but recommended).
Returns <script> tags for:
- Injected files (from
js.injectpatterns) - Bundled files (from manifest)
Returns <link> tags for:
- Injected files (from
styles.injectpatterns) - Compiled styles (from manifest)
Install the required plugin:
npm install @rsbuild/plugin-less --save-devAnd add it to your config:
const { pluginLess } = require('@rsbuild/plugin-less')
module.exports.shipwright = {
build: { plugins: [pluginLess()] }
}Check that your inject patterns don't overlap with files in the bundle. Shipwright automatically deduplicates, but explicit is better than implicit.
Ensure NODE_ENV is not set to production in development.
The Sails framework is free and open-source under the MIT License.