jalla 1.0.0-4 → 1.0.0-40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,7 +28,7 @@ compiled using [Documentify][documentify]. And it's all configured for you.
28
28
  - [`ctx.assets`](#ctxassets)
29
29
  - [Assets](#assets)
30
30
  - [Manifest](#manifest)
31
- - [Service Workers](#service-workers)
31
+ - [Service Workers](#service-workers)
32
32
  - [Advanced Usage](#advanced-usage)
33
33
  - [Configuration](#configuration)
34
34
  - [JavaScript](#javascript)
@@ -70,6 +70,7 @@ $ NODE_ENV=production jalla index.js
70
70
  - __`--css`__ explicitly include a css file in the build
71
71
  - __`--service-worker, --sw`__ entry point for a service worker
72
72
  - __`--base, -b`__ base path where app will be served
73
+ - __`--skip, -s`__ skip transform for file/glob (excluding optimizations)
73
74
  - __`--watch, -w`__ watch files for changes (default in `development`)
74
75
  - __`--dir, -d`__ output directory, use with [build](#build) and [serve](#serve)
75
76
  - __`--quiet, -q`__ disable printing to console
@@ -178,11 +179,13 @@ before issuing another render pass using the state generated the first time.
178
179
 
179
180
  ```javascript
180
181
  // store.js
182
+ var fetch = require('node-fetch')
183
+
181
184
  module.exports = function (state, emitter) {
182
185
  state.data = state.data || null
183
186
 
184
187
  emitter.on('fetch', function () {
185
- var request = window.fetch('/my/api')
188
+ var request = fetch('/my/api')
186
189
  .then((res) => res.json())
187
190
  .then(function (data) {
188
191
  state.data = data
@@ -258,7 +261,7 @@ A bare-bones application manifest is generated based on the projects
258
261
  `package.json`. You can either place a custom `manifest.json` in the
259
262
  [assets](#assets) folder or you can generate one using a custom middleware.
260
263
 
261
- ### Service Workers
264
+ ## Service Workers
262
265
  By supplying the path to a service worker entry file with the `sw` option, jalla
263
266
  will build and serve it's bundle from that path.
264
267
 
@@ -313,7 +316,13 @@ self.addEventListener('install', function oninstall (event) {
313
316
 
314
317
  self.addEventListener('activate', function onactivate (event) {
315
318
  // clear old caches on activate
316
- event.waitUntil(clear().then(() => self.clients.claim()))
319
+ event.waitUntil(clear().then(function () {
320
+ if (!self.registration.navigationPreload) return self.clients.claim()
321
+ // enable navigation preload
322
+ return self.registration.navigationPreload.enable().then(function () {
323
+ return self.clients.claim()
324
+ })
325
+ }))
317
326
  })
318
327
 
319
328
  self.addEventListener('fetch', function onfetch (event) {
@@ -321,14 +330,14 @@ self.addEventListener('fetch', function onfetch (event) {
321
330
  event.respondWith(caches.open(CACHE_KEY).then(async function (cache) {
322
331
  try {
323
332
  var cached = await cache.match(req)
324
- var response = self.fetch(event.request)
325
- if (req.method.toUpperCase() === 'GET') {
333
+ var response = await (event.preloadResponse || self.fetch(event.request))
334
+ if (response.ok && req.method.toUpperCase() === 'GET') {
326
335
  await cache.put(req, response.clone())
327
336
  }
328
337
  return response
329
338
  } catch (err) {
330
339
  if (cached) return cached
331
- throw err
340
+ return err
332
341
  }
333
342
  }))
334
343
  })
@@ -369,24 +378,32 @@ is compiling the app. The pipline steps are called in series, and have access
369
378
  to the assets and dependencies of all prior steps.
370
379
 
371
380
  ```javascript
372
- var fs = require('fs')
381
+ var path = require('path')
373
382
  var jalla = require('jalla')
383
+ var csv = require('csvtojson')
374
384
  var app = jalla('index.js')
375
385
 
376
- // include data.csv as an asset
386
+ // convert and include data.csv as a JSON file
377
387
  app.pipeline.get('assets').push(function (state, emit) {
378
- return function (cb) {
379
- emit('progress', 'data.csv')
380
-
381
- fs.readFile('data.csv', function (err, buffer) {
382
- if (err) return cb(err)
383
- emit('asset', 'data.json', buffer)
384
- cb()
388
+ return async function (cb) {
389
+ if (state.assets.has('data.json')) return cb()
390
+ emit('progress', 'data.json')
391
+ var json = await csv.fromFile(path.resolve(state.entry, 'data.csv'))
392
+ emit('asset', 'data.json', Buffer.from(JSON.stringify(json)), {
393
+ mime: 'application/json
385
394
  })
395
+ cb()
386
396
  }
387
397
  })
388
398
 
389
- app.listen(8080)
399
+ if (process.env.BUILD) {
400
+ app.build(path.resolve(__dirname, 'dist'), function (err) {
401
+ if (err) console.error(err)
402
+ process.exit(err ? 1 : 0)
403
+ })
404
+ } else {
405
+ app.listen(8080)
406
+ }
390
407
  ```
391
408
 
392
409
  ## Configuration
package/bin.js CHANGED
@@ -8,7 +8,6 @@ var assert = require('assert')
8
8
  var dedent = require('dedent')
9
9
  var getPort = require('get-port')
10
10
  var minimist = require('minimist')
11
- var App = require('./lib/app')
12
11
  var jalla = require('./index')
13
12
 
14
13
  var COMMANDS = ['start', 'build', 'serve']
@@ -16,14 +15,15 @@ var COMMANDS = ['start', 'build', 'serve']
16
15
  var argv = minimist(process.argv.slice(2), {
17
16
  alias: {
18
17
  'service-worker': 'sw',
19
- 'dir': 'd',
20
- 'quiet': 'q',
21
- 'inspect': 'i',
22
- 'base': 'b',
23
- 'watch': 'w',
24
- 'port': 'p',
25
- 'help': 'h',
26
- 'version': 'v'
18
+ dir: 'd',
19
+ quiet: 'q',
20
+ inspect: 'i',
21
+ skip: 's',
22
+ base: 'b',
23
+ watch: 'w',
24
+ port: 'p',
25
+ help: 'h',
26
+ version: 'v'
27
27
  },
28
28
  default: {
29
29
  port: process.env.PORT || 8080
@@ -49,6 +49,7 @@ if (argv.help) {
49
49
  --css entry point for CSS
50
50
  --service-worker, --sw entry point for service worker
51
51
  --base, -b base path where app will be mounted
52
+ --skip, -s skip transform for file/glob (excluding optimizations)
52
53
  --watch, -w enable watch mode (default in development)
53
54
  --dir, -d output directory, use with ${chalk.bold('build')} and ${chalk.bold('serve')}
54
55
  --quiet, -q disable printing to console
@@ -95,14 +96,16 @@ if (typeof argv.watch !== 'undefined') opts.watch = Boolean(argv.watch)
95
96
 
96
97
  if (command === 'build') {
97
98
  opts.watch = false
98
- let app = new App(path.resolve(process.cwd(), entry), opts)
99
- let dir = typeof argv.dir === 'string' ? argv.dir : 'dist'
100
- app.build(path.resolve(process.cwd(), dir), function (err) {
101
- process.exit(err ? 1 : 0)
99
+ const app = jalla(path.resolve(process.cwd(), entry), opts)
100
+ const dir = typeof argv.dir === 'string' ? argv.dir : 'dist'
101
+ app.build(path.resolve(process.cwd(), dir)).then(function () {
102
+ process.exit(0)
103
+ }, function () {
104
+ process.exit(1)
102
105
  })
103
106
  } else {
104
- let app = jalla(path.resolve(process.cwd(), entry), opts)
105
- getPort({ port: argv.port }).then(function (port) {
107
+ const app = jalla(path.resolve(process.cwd(), entry), opts)
108
+ getPort({ port: argv.port || 8080 }).then(function (port) {
106
109
  app.listen(port)
107
110
  })
108
111
  }
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  var path = require('path')
2
2
  var assert = require('assert')
3
3
  var serve = require('koa-static')
4
+ var { Minimatch } = require('minimatch')
4
5
  var App = require('./lib/app')
5
6
  var render = require('./lib/render')
6
7
 
@@ -14,11 +15,24 @@ function start (entry, opts = {}) {
14
15
  var dist = opts.dist
15
16
  if (!dist) dist = typeof opts.serve === 'string' ? opts.serve : 'dist'
16
17
 
18
+ var swPath = opts.sw
19
+ ? path.resolve(dir, dist, 'public', path.relative(dir, opts.sw))
20
+ : null
21
+
22
+ if (opts.skip) {
23
+ const input = Array.isArray(opts.skip) ? opts.skip : [opts.skip]
24
+ var skip = input.map(normalizeSkip)
25
+ }
26
+
17
27
  opts = Object.assign({}, opts, {
18
28
  dist: absolute(dist, dir),
19
29
  serve: Boolean(opts.serve),
20
30
  sw: opts.sw && absolute(opts.sw, dir),
21
- css: opts.css && absolute(opts.css, dir)
31
+ css: opts.css && absolute(opts.css, dir),
32
+ skip (file) {
33
+ if (!skip) return false
34
+ return skip.reduce((res, test) => res || test(file), false)
35
+ }
22
36
  })
23
37
 
24
38
  var app = new App(entry, opts)
@@ -29,45 +43,44 @@ function start (entry, opts = {}) {
29
43
  app.emit('timing', Date.now() - start, ctx)
30
44
  })
31
45
 
32
- if (opts.serve) {
33
- let pub = path.resolve(opts.dist, 'public')
34
- app.use(serve(pub, { setHeaders }))
35
- } else {
36
- let state = Object.assign({
37
- env: app.env,
38
- base: opts.base || '',
39
- watch: typeof opts.watch === 'undefined'
40
- ? app.env === 'development'
41
- : opts.watch
42
- }, opts)
43
- let init = new Promise(function (resolve, reject) {
44
- app.pipeline.bundle(entry, state, resolve)
45
- })
46
-
47
- app.use((ctx, next) => init.then(next))
48
- app.use(app.pipeline.middleware())
49
- app.use(function (ctx, next) {
50
- if (ctx.body) {
51
- let cache = state.env !== 'development' && !state.watch
52
- let maxAge = cache ? 60 * 60 * 24 * 365 : 0
53
- let value = `${cache ? 'public, ' : ''}max-age=${maxAge}`
54
- ctx.set('Cache-Control', value)
55
- }
56
- return next()
57
- })
58
- }
59
-
60
46
  app.use(require('koa-conditional-get')())
61
47
  app.use(require('koa-etag')())
62
48
  app.use(render(app))
63
49
 
50
+ if (opts.serve) {
51
+ app.use(serve(path.resolve(opts.dist, 'public'), { setHeaders }))
52
+ } else {
53
+ app.use(app.pipeline.middleware(app.state))
54
+ }
55
+
64
56
  return app
57
+
58
+ // set static asset headers
59
+ // (obj, str, obj) -> void
60
+ function setHeaders (res, filepath, stats) {
61
+ if (filepath === swPath) {
62
+ res.setHeader('Cache-Control', 'max-age=0')
63
+ } else {
64
+ res.setHeader('Cache-Control', `public, max-age=${60 * 60 * 24 * 365}`)
65
+ }
66
+ }
65
67
  }
66
68
 
67
- // set static asset headers
68
- // (obj, str, obj) -> void
69
- function setHeaders (res, path, stats) {
70
- res.setHeader('Cache-Control', `public, maxage=${60 * 60 * 24 * 365}`)
69
+ // ensure skip input is a function
70
+ // any -> fn
71
+ function normalizeSkip (val) {
72
+ if (val instanceof RegExp) {
73
+ return val.test.bind(val)
74
+ } else if (typeof val === 'function') {
75
+ return val
76
+ } else if (typeof val === 'string') {
77
+ var minimatch = new Minimatch(val)
78
+ return function (str) {
79
+ return str.includes(val) || minimatch.match(str)
80
+ }
81
+ } else {
82
+ throw new Error('jalla: skip should be either RegExp, function or string')
83
+ }
71
84
  }
72
85
 
73
86
  // resolve file path (relative to dir) to absolute path
package/lib/app.js CHANGED
@@ -1,7 +1,8 @@
1
+ var fs = require('fs')
1
2
  var Koa = require('koa')
2
3
  var path = require('path')
3
4
  var assert = require('assert')
4
- var mkdirp = require('mkdirp')
5
+ var browserslist = require('browserslist')
5
6
  var ui = require('./ui')
6
7
  var build = require('./build')
7
8
  var style = require('./style')
@@ -12,28 +13,37 @@ var manifest = require('./manifest')
12
13
  var Pipeline = require('./pipeline')
13
14
  var serviceWorker = require('./service-worker')
14
15
 
16
+ var DEFAULT_BROWSERS = [
17
+ 'last 2 Chrome versions',
18
+ 'last 2 Firefox versions',
19
+ 'last 2 Safari versions',
20
+ 'last 2 Edge versions',
21
+ '> 1%'
22
+ ]
23
+
15
24
  module.exports = class App extends Koa {
16
25
  constructor (entry, opts) {
17
26
  super()
18
27
 
19
- this.base = opts.base || ''
20
-
21
28
  var bundled = []
22
29
  if (opts.serve) {
23
30
  try {
24
31
  // pick up stat of existing build
25
- let stat = require(path.resolve(opts.dist, 'stat.json'))
32
+ const stat = require(path.resolve(opts.dist, 'stat.json'))
26
33
  bundled = stat.assets.map(function (asset) {
27
34
  return Object.assign({}, asset, {
28
35
  hash: Buffer.from(asset.hash, 'hex'),
29
36
  file: path.resolve(opts.dist, 'public', asset.file)
30
37
  })
31
38
  })
32
- // use bundled entry for faster startup time
33
- entry = path.resolve(opts.dist, path.basename(entry))
39
+ this.browsers = stat.browsers
34
40
  } catch (err) {
35
41
  this.emit('error', Error('Failed to load stat from serve directory'))
36
42
  }
43
+ } else {
44
+ const dir = path.dirname(entry)
45
+ const browsers = browserslist.loadConfig({ path: dir, env: this.env })
46
+ this.browsers = browsers || DEFAULT_BROWSERS
37
47
  }
38
48
 
39
49
  var pipeline = new Pipeline([
@@ -45,6 +55,8 @@ module.exports = class App extends Koa {
45
55
  ['build']
46
56
  ], bundled)
47
57
 
58
+ this.bundled = false
59
+ this.base = opts.base || ''
48
60
  this.entry = entry
49
61
  this._opts = opts
50
62
  this.pipeline = pipeline
@@ -62,20 +74,26 @@ module.exports = class App extends Koa {
62
74
  else this.silent = true
63
75
  }
64
76
 
77
+ get state () {
78
+ return Object.assign({
79
+ browsers: this.browsers,
80
+ base: this.base,
81
+ env: this.env,
82
+ watch: this.env === 'development'
83
+ }, this._opts)
84
+ }
85
+
65
86
  // write assets to disk
66
87
  // (str, fn) -> void
67
88
  build (dir, state) {
68
89
  assert(typeof dir === 'string', 'jalla:build dir should be type string')
69
90
 
70
- state = Object.assign({
71
- base: this.base,
72
- env: this.env,
73
- watch: false,
74
- dist: dir
75
- }, this._opts, state)
91
+ if (!path.isAbsolute(dir)) dir = path.resolve(dir)
92
+
93
+ state = Object.assign({}, this.state, state, { dist: dir, watch: false })
76
94
 
77
95
  return new Promise((resolve, reject) => {
78
- mkdirp(dir, (err) => {
96
+ fs.mkdir(dir, { recursive: true }, (err) => {
79
97
  if (err) return reject(err)
80
98
  var index = this.pipeline.get('build').push(build)
81
99
  this.pipeline.bundle(this.entry, state, (err) => {
@@ -87,12 +105,24 @@ module.exports = class App extends Koa {
87
105
  })
88
106
  }
89
107
 
90
- // start server
91
- // (num, fn) -> void
92
- listen (port, cb) {
93
- super.listen(port, () => {
94
- this.emit('start', port)
95
- if (typeof cb === 'function') cb()
108
+ bundle () {
109
+ this.bundled = true
110
+ var init = new Promise((resolve, reject) => {
111
+ this.pipeline.bundle(this.entry, this.state, function (err) {
112
+ if (err) return reject(err)
113
+ resolve()
114
+ })
115
+ })
116
+ this.middleware.unshift((ctx, next) => init.then(next))
117
+ return init
118
+ }
119
+
120
+ listen (port = 8080, cb) {
121
+ var self = this
122
+ if (!this.state.serve && !this.bundled) this.bundle()
123
+ return super.listen(port, function () {
124
+ self.emit('start', port)
125
+ if (typeof cb === 'function') return cb.apply(this, arguments)
96
126
  })
97
127
  }
98
128
  }
package/lib/build.js CHANGED
@@ -1,86 +1,14 @@
1
1
  var fs = require('fs')
2
2
  var util = require('util')
3
3
  var path = require('path')
4
- var brfs = require('brfs')
5
- var mkdirp = require('mkdirp')
6
- var crypto = require('crypto')
7
- var tfilter = require('tfilter')
8
- var through = require('through2')
9
- var babelify = require('babelify')
10
- var exorcist = require('exorcist')
11
- var uglifyify = require('uglifyify')
12
- var browserify = require('browserify')
13
- var unassertify = require('unassertify')
14
- var shakeify = require('common-shakeify')
15
- var splitRequire = require('split-require')
16
- var babelPresetEnv = require('@babel/preset-env')
17
- var inject = require('./inject')
18
4
 
19
- var createDir = util.promisify(mkdirp)
5
+ var createDir = util.promisify(fs.mkdir)
20
6
  var writeFile = util.promisify(fs.writeFile)
21
7
 
22
- module.exports = ssr
23
-
24
- function ssr (state, emit) {
25
- var name = path.basename(state.entry)
26
- var b = browserify(state.entry, {
27
- node: true,
28
- debug: true,
29
- ignoreMissing: true,
30
- // preserve paths for split-require to resolve during runtime
31
- fullPaths: true,
32
- standalone: name
33
- })
34
-
35
- capture()
36
- b.on('reset', capture)
37
-
38
- b.plugin(splitRequire, {
39
- public: state.dist,
40
- filename: dynamicBundleName,
41
- output: bundleDynamicBundle
42
- })
43
-
44
- b.on('split.pipeline', function (pipeline, entry, name) {
45
- var map = path.resolve(state.dist, name + '.map')
46
- pipeline.get('wrap').push(exorcist(map))
47
- })
48
-
49
- b.transform(tfilter(babelify, { exclude: /node_modules/ }), {
50
- plugins: ['dynamic-import-split-require'],
51
- babelrc: false,
52
- presets: [
53
- [babelPresetEnv, {
54
- targets: { node: 'current' }
55
- }]
56
- ]
57
- })
58
-
59
- b.transform(brfs)
60
- b.transform(inject('source-map-support/register', state.entry))
61
-
62
- if (state.env !== 'development') {
63
- b.transform(unassertify, { global: true })
64
- b.transform(uglifyify, { global: true })
65
- b.plugin(shakeify)
66
- }
67
-
68
- b.on('reset', function restart () {
69
- emit('progress', name, 0)
70
- })
8
+ module.exports = build
71
9
 
10
+ function build (state, emit) {
72
11
  return function bundle (cb) {
73
- var map = path.resolve(state.dist, name + '.map')
74
- var ssr = new Promise(function (resolve, reject) {
75
- b.bundle().on('error', reject)
76
- // can't run terser in tandem due it generating octal escaped sequences
77
- // in template strings which it then can't parse
78
- // .pipe(state.env !== 'development' ? minify() : through())
79
- .pipe(state.env !== 'development' ? exorcist(map) : through())
80
- .pipe(fs.createWriteStream(path.resolve(state.dist, name), 'utf8'))
81
- .on('close', resolve)
82
- })
83
-
84
12
  var re = new RegExp(`^(?:${state.base.replace(/\//g, '\\/')})?\\/`)
85
13
  var assets = Array.from(state.assets.values(), function (asset) {
86
14
  asset.file = asset.url.replace(re, '')
@@ -88,70 +16,26 @@ function ssr (state, emit) {
88
16
  })
89
17
 
90
18
  var stat = JSON.stringify({
91
- entry: name,
19
+ browsers: state.browsers,
92
20
  assets: assets
93
21
  }, stringify, 2)
94
22
 
95
23
  emit('progress', 'stat.json', 0)
96
24
 
97
25
  Promise.all([
98
- ssr,
99
- template(),
100
- ...assets.map(async function (asset) {
101
- var dest = path.resolve(state.dist, 'public', asset.file)
26
+ Promise.all(assets.map(async function (asset) {
27
+ var file = path.resolve(state.dist, 'public', asset.file)
102
28
  emit('progress', asset.id, 0)
103
- await createDir(path.dirname(dest))
104
- return writeFile(dest, asset.buffer)
105
- }),
29
+ try {
30
+ await createDir(path.dirname(file), { recursive: true })
31
+ } catch (err) {
32
+ // Ignore failed `mkdir` and try writing file anyway
33
+ }
34
+ return writeFile(file, asset.buffer)
35
+ })),
106
36
  writeFile(path.resolve(state.dist, 'stat.json'), stat)
107
37
  ]).then(cb.bind(undefined, null), cb)
108
38
  }
109
-
110
- // copy template file to dist
111
- // () -> Promise
112
- function template () {
113
- return new Promise(function (resolve, reject) {
114
- var dir = path.join(path.dirname(state.entry), 'index')
115
- resolve('.', { basedir: dir, extensions: ['.html'] }, function (err, file) {
116
- if (err) return resolve()
117
- emit('progress', file, 0)
118
- fs.readFile(file, function (err, buf) {
119
- if (err) return reject(err)
120
- resolve(writeFile(path.resolve(state.dist, 'index.html'), buf))
121
- })
122
- })
123
- })
124
- }
125
-
126
- // capture bundle dependencies from pipeline
127
- // () -> void
128
- function capture () {
129
- emit('reset')
130
- b.pipeline.get('deps').push(through.obj(function (row, enc, next) {
131
- var file = row.expose ? b._expose[row.id] : row.file
132
- emit('dep', file)
133
- this.push(row)
134
- next()
135
- }))
136
- }
137
-
138
- // generate name for dýnamic bundle
139
- // obj -> str
140
- function dynamicBundleName (record) {
141
- var basename = path.basename(record.sourceFile, '.js')
142
- var isIndex = basename === 'index'
143
- var id = basename
144
- if (isIndex) id = path.dirname(record.sourceFile).split('/').slice(-1)[0]
145
- var buff = Buffer.from(record.source)
146
- var hash = crypto.createHash('sha512').update(buff).digest('buffer')
147
- return `${id}-${hash.toString('hex').slice(0, 16)}.js`
148
- }
149
-
150
- // handle dynamic bundle
151
- // str -> stream.Writable
152
- function bundleDynamicBundle (name) {
153
- return fs.createWriteStream(path.resolve(state.dist, name), 'utf8')
154
- }
155
39
  }
156
40
 
157
41
  // JSON.stringify replacer
package/lib/compile.js CHANGED
@@ -3,6 +3,7 @@ var { addHook } = require('pirates')
3
3
  var clearModule = require('clear-module')
4
4
 
5
5
  var SCRIPT = /\.js$/
6
+ var NODE_MODULES = /node_modules/
6
7
 
7
8
  module.exports = compile
8
9
 
@@ -10,14 +11,16 @@ function compile (state, emit) {
10
11
  addHook(hook, { matcher })
11
12
 
12
13
  return function (cb) {
13
- for (let dep of state.deps) {
14
- if (SCRIPT.test(dep)) clearModule(dep)
14
+ for (const dep of state.deps) {
15
+ if (SCRIPT.test(dep) && !NODE_MODULES.test(dep) && !state.skip(dep)) {
16
+ clearModule(dep)
17
+ }
15
18
  }
16
19
  cb()
17
20
  }
18
21
 
19
22
  function matcher (file) {
20
- if (/node_modules/.test(file)) return false
23
+ if (NODE_MODULES.test(file) || state.skip(file)) return false
21
24
  return state.deps.has(file)
22
25
  }
23
26