├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── client.js ├── example.js ├── index.html ├── index.js ├── package.json ├── tap-snapshots └── test.js-TAP.test.js └── test.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: npm 9 | directory: '/' 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | pull_request: 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | 13 | jobs: 14 | test: 15 | runs-on: ${{ matrix.os }} 16 | 17 | strategy: 18 | matrix: 19 | node-version: [6.x, 8.x, 9.x] 20 | os: [macOS-latest, windows-latest, ubuntu-latest] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Use Node.js 26 | uses: actions/setup-node@v2.1.5 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | 30 | - name: Install Dependencies 31 | run: | 32 | npm install --ignore-scripts 33 | 34 | - name: Run Tests 35 | run: | 36 | npm run test:ci 37 | 38 | - name: Coveralls Parallel 39 | uses: coverallsapp/github-action@v1.1.2 40 | with: 41 | github-token: ${{ secrets.github_token }} 42 | parallel: true 43 | flag-name: run-${{ matrix.node-version }}-${{ matrix.os }} 44 | 45 | coverage: 46 | needs: test 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Coveralls Finished 50 | uses: coverallsapp/github-action@v1.1.2 51 | with: 52 | github-token: ${{ secrets.GITHUB_TOKEN }} 53 | parallel-finished: true 54 | 55 | automerge: 56 | needs: test 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: fastify/github-action-merge-dependabot@v1.2.1 60 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' }} 61 | with: 62 | github-token: ${{secrets.github_token}} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # Vim swap files 119 | *.swp 120 | 121 | # macOS files 122 | .DS_Store 123 | 124 | # lock files 125 | package-lock.json 126 | yarn.lock 127 | 128 | # editor files 129 | .vscode 130 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fastify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastify-bankai 2 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://travis-ci.org/fastify/fastify-bankai.svg?branch=master)](https://travis-ci.org/fastify/fastify-bankai) [![Greenkeeper badge](https://badges.greenkeeper.io/fastify/fastify-bankai.svg)](https://greenkeeper.io/) 3 | 4 | If you need to compile (browserify style!) your code, this plugin is for you! 5 | Internally it uses [bankai](https://github.com/yoshuawuyts/bankai), so refer to its documentation for the options. 6 | 7 | **fastify-bankai** will automatically live-reload your HTML and 8 | regenerate your bundle whenever your code change. This can be disabled 9 | in test or in production. 10 | 11 | ## Install 12 | ``` 13 | npm i fastify-bankai --save 14 | ``` 15 | 16 | ## Usage 17 | Simply require this plugin, pass the entry file and you are done! 18 | ```js 19 | const fastify = require('fastify')() 20 | 21 | fastify.register(require('fastify-bankai'), { 22 | entry: './client.js' 23 | }) 24 | 25 | fastify.listen(3000, err => { 26 | if (err) throw err 27 | console.log('Server listenting on localhost:', fastify.server.address().port) 28 | }) 29 | ``` 30 | 31 | ### In tests or in production 32 | 33 | If you are including fastify-bankai in any test run or in production, you **must** disable 34 | the automatic watch mode: 35 | 36 | ```js 37 | const fastify = require('fastify')() 38 | 39 | fastify.register(require('fastify-bankai'), { 40 | entry: './client.js', 41 | watch: false 42 | }) 43 | 44 | fastify.listen(3000, err => { 45 | if (err) throw err 46 | console.log('Server listenting on localhost:', fastify.server.address().port) 47 | }) 48 | ``` 49 | 50 | ## Options 51 | 52 | - `entry`: Your application entry point 53 | - `prefix`: prefix all paths served by fastify-bankai with the given 54 | path 55 | 56 | The option object is passed directly to bankai. 57 | 58 | ## Acknowledgements 59 | 60 | This project is kindly sponsored by: 61 | - [nearForm](http://nearform.com) 62 | - [LetzDoIt](http://www.letzdoitapp.com/) 63 | 64 | ## License 65 | 66 | Licensed under [MIT](./LICENSE). 67 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | document.write('hello world!') 4 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fastify = require('fastify')() 4 | 5 | fastify.register(require('./index'), { 6 | entry: './client.js', 7 | html: './index.html' 8 | }) 9 | 10 | fastify.listen(3000, err => { 11 | if (err) throw err 12 | console.log('Server listenting on localhost:', fastify.server.address().port) 13 | }) 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const createReadStream = require('fs').createReadStream 4 | const resolve = require('path').resolve 5 | const bankai = require('bankai') 6 | const fp = require('fastify-plugin') 7 | 8 | function fastifyBankai (fastify, opts, next) { 9 | if (!opts.entry) { 10 | return next(new Error('missing entry file')) 11 | } 12 | if (typeof opts.entry !== 'string') { 13 | return next(new Error('entry must be a string')) 14 | } 15 | if (opts.html && typeof opts.html !== 'string') { 16 | return next(new Error('html must be a string')) 17 | } 18 | 19 | var prefix = (opts.prefix || '').replace(/^\/?(.*)\/+$/, '/$1') 20 | delete opts.prefix 21 | const html = !!opts.html 22 | const htmlPath = resolve(opts.html || '') 23 | 24 | // be quiet by default 25 | if (opts.quiet === undefined) { 26 | opts.quiet = true 27 | } 28 | 29 | const compiler = bankai(resolve(opts.entry), opts) 30 | 31 | if (prefix && prefix.indexOf('/') !== 0) { 32 | prefix = '/' + prefix 33 | } 34 | 35 | const rootRoute = `${prefix || '/'}` 36 | 37 | // TODO is this needed? 38 | if (html) { 39 | fastify.get(rootRoute, (req, reply) => { 40 | reply 41 | .header('Content-Type', 'text/html') 42 | .send(createReadStream(htmlPath)) 43 | }) 44 | } else { 45 | fastify.get(rootRoute, (req, reply) => { 46 | compiler.documents('/', (err, data) => { 47 | if (err) { 48 | reply.send(err) 49 | return 50 | } 51 | reply 52 | .header('content-type', 'text/html') 53 | .send(data.buffer) 54 | }) 55 | }) 56 | } 57 | 58 | fastify.get(`${prefix}/:file`, (req, reply) => { 59 | const [file, ext] = req.params.file.split('.') 60 | switch (ext) { 61 | case 'html': 62 | compiler.documents(req.req.url, (err, data) => { 63 | if (err) { 64 | reply.send(err) 65 | return 66 | } 67 | reply 68 | .header('content-type', 'text/html') 69 | .send(data) 70 | }) 71 | break 72 | case 'js': 73 | compiler.scripts(file, (err, data) => { 74 | if (err) { 75 | reply.send(err) 76 | return 77 | } 78 | reply 79 | .header('content-type', 'application/javascript') 80 | .send(data.buffer) 81 | }) 82 | break 83 | case 'css': 84 | compiler.styles(file, (err, data) => { 85 | if (err) { 86 | reply.send(err) 87 | return 88 | } 89 | reply 90 | .header('content-type', 'text/css') 91 | .send(data.buffer) 92 | }) 93 | break 94 | default: 95 | compiler.assets(req.req.url, (err, data) => { 96 | if (err) { 97 | return reply 98 | .code(404) 99 | .send(err) 100 | } 101 | 102 | // TODO set the correct mime type? 103 | reply.send(data.buffer) 104 | }) 105 | } 106 | }) 107 | 108 | fastify.addHook('onClose', (f, done) => { 109 | compiler.close() 110 | setImmediate(done) 111 | }) 112 | 113 | compiler.once('change', () => { next() }) 114 | } 115 | 116 | module.exports = fp(fastifyBankai, { 117 | name: 'fastify-bankai', 118 | fastify: '>= 0.40.0' 119 | }) 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastify-bankai", 3 | "version": "0.3.1", 4 | "description": "Bankai assets compiler for Fastify", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard && tap test.js", 8 | "test:ci": "standard && tap test.js --coverage-report=lcovonly" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/fastify/fastify-bankai.git" 13 | }, 14 | "keywords": [ 15 | "browserify", 16 | "bankai", 17 | "assets", 18 | "html", 19 | "css", 20 | "js", 21 | "fastify", 22 | "compile" 23 | ], 24 | "author": "Tomas Della Vedova - @delvedor (http://delved.org)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/fastify/fastify-bankai/issues" 28 | }, 29 | "homepage": "https://github.com/fastify/fastify-bankai#readme", 30 | "devDependencies": { 31 | "fastify": "^0.40.0", 32 | "pre-commit": "^1.2.2", 33 | "standard": "^10.0.3", 34 | "tap": "^11.0.0" 35 | }, 36 | "dependencies": { 37 | "bankai": "^9.4.0", 38 | "fastify-plugin": "^0.2.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tap-snapshots/test.js-TAP.test.js: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test.js TAP expose a route with the assets > undefined 1`] = ` 9 | 10 | ` 11 | 12 | exports[`test.js TAP handle a prefix: /test > undefined 1`] = ` 13 | 14 | ` 15 | 16 | exports[`test.js TAP handle a prefix: /test/ > undefined 1`] = ` 17 | 18 | ` 19 | 20 | exports[`test.js TAP handle a prefix: test > undefined 1`] = ` 21 | 22 | ` 23 | 24 | exports[`test.js TAP handle a prefix: test/ > undefined 1`] = ` 25 | 26 | ` 27 | 28 | exports[`test.js TAP handle a given html file > undefined 1`] = ` 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ` 46 | 47 | exports[`test.js TAP close if watch is true > undefined 1`] = ` 48 | 49 | ` 50 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const test = t.test 5 | const Fastify = require('fastify') 6 | 7 | test('expose a route with the assets', t => { 8 | t.plan(10) 9 | 10 | const fastify = Fastify() 11 | fastify.register( 12 | require('./index'), 13 | { entry: './client.js', watch: false } 14 | ) 15 | t.teardown(fastify.close.bind(fastify)) 16 | 17 | fastify.inject({ 18 | url: '/', 19 | method: 'GET' 20 | }, (err, res) => { 21 | t.error(err) 22 | t.equal(res.statusCode, 200) 23 | t.matchSnapshot(res.payload) 24 | t.equal(res.headers['content-type'], 'text/html') 25 | }) 26 | 27 | fastify.inject({ 28 | url: '/bundle.css', 29 | method: 'GET' 30 | }, (err, res) => { 31 | t.error(err) 32 | t.equal(res.statusCode, 200) 33 | t.equal(res.headers['content-type'], 'text/css') 34 | }) 35 | 36 | fastify.inject({ 37 | url: '/bundle.js', 38 | method: 'GET' 39 | }, (err, res) => { 40 | t.error(err) 41 | t.equal(res.statusCode, 200) 42 | t.equal(res.headers['content-type'], 'application/javascript') 43 | }) 44 | }) 45 | 46 | function testPrefix (prefix) { 47 | test('handle a prefix: ' + prefix, t => { 48 | t.plan(9) 49 | 50 | const fastify = Fastify() 51 | fastify.register( 52 | require('./index'), 53 | { entry: './client.js', prefix, watch: false } 54 | ) 55 | t.teardown(fastify.close.bind(fastify)) 56 | 57 | fastify.inject({ 58 | url: '/test', 59 | method: 'GET' 60 | }, (err, res) => { 61 | t.error(err) 62 | t.equal(res.statusCode, 200) 63 | t.matchSnapshot(res.payload) 64 | }) 65 | 66 | fastify.inject({ 67 | url: '/test/bundle.css', 68 | method: 'GET' 69 | }, (err, res) => { 70 | t.error(err) 71 | t.equal(res.statusCode, 200) 72 | t.equal(res.headers['content-type'], 'text/css') 73 | }) 74 | 75 | fastify.inject({ 76 | url: '/test/bundle.js', 77 | method: 'GET' 78 | }, (err, res) => { 79 | t.error(err) 80 | t.equal(res.statusCode, 200) 81 | t.equal(res.headers['content-type'], 'application/javascript') 82 | }) 83 | }) 84 | } 85 | 86 | testPrefix('/test') 87 | testPrefix('/test/') 88 | testPrefix('test') 89 | testPrefix('test/') 90 | 91 | test('handle a given html file', t => { 92 | t.plan(3) 93 | 94 | const fastify = Fastify() 95 | fastify.register( 96 | require('./index'), 97 | { entry: './client.js', html: './index.html', watch: false } 98 | ) 99 | t.teardown(fastify.close.bind(fastify)) 100 | 101 | fastify.inject({ 102 | url: '/', 103 | method: 'GET' 104 | }, (err, res) => { 105 | t.error(err) 106 | t.equal(res.statusCode, 200) 107 | t.matchSnapshot(res.payload) 108 | }) 109 | }) 110 | 111 | test('return 404 if an assets is not found', t => { 112 | t.plan(2) 113 | 114 | const fastify = Fastify() 115 | fastify.register( 116 | require('./index'), 117 | { entry: './client.js', watch: false } 118 | ) 119 | 120 | t.teardown(fastify.close.bind(fastify)) 121 | 122 | fastify.inject({ 123 | url: '/file.php', 124 | method: 'GET' 125 | }, (err, res) => { 126 | t.error(err) 127 | t.equal(res.statusCode, 404) 128 | }) 129 | }) 130 | 131 | if (!process.env.TRAVIS) { 132 | test('close if watch is true', t => { 133 | t.plan(4) 134 | 135 | const fastify = Fastify() 136 | fastify.register( 137 | require('./index'), 138 | { entry: './client.js' } 139 | ) 140 | t.teardown(fastify.close.bind(fastify)) 141 | 142 | fastify.inject({ 143 | url: '/', 144 | method: 'GET' 145 | }, (err, res) => { 146 | t.error(err) 147 | t.equal(res.statusCode, 200) 148 | t.matchSnapshot(res.payload) 149 | t.equal(res.headers['content-type'], 'text/html') 150 | }) 151 | }) 152 | } 153 | 154 | test('error if entry is missing', t => { 155 | t.plan(2) 156 | 157 | const fastify = Fastify() 158 | fastify.register( 159 | require('./index') 160 | ) 161 | t.teardown(fastify.close.bind(fastify)) 162 | 163 | fastify.ready((err) => { 164 | t.ok(err) 165 | t.equal(err.message, 'missing entry file') 166 | }) 167 | }) 168 | --------------------------------------------------------------------------------