UNPKG

8.64 kBJavaScriptView Raw
1#!/usr/bin/env node
2'use strict'
3
4var fs = require('fs')
5var path = require('path')
6var http = require('http')
7var debounce = require('lodash.debounce')
8
9var APP_ROOT = process.cwd()
10var SRC_ROOT = path.join(APP_ROOT, 'src')
11var express = require('express')
12var st = require('st')
13var xtend = require('xtend')
14var Promise = require('bluebird')
15var through = require('through2');
16
17var _ = require('lodash');
18var socketio = require('socket.io');
19var has = require('./browserify-hmr/lib/has');
20
21var chokidar = require('chokidar')
22var watcher = chokidar.watch(SRC_ROOT, {
23 cwd: SRC_ROOT,
24 ignored: [
25 /[\/\\]\./
26 ],
27 ignoreInitial: true
28})
29
30var bundle = require('./bundle')
31var args = {
32 fullPaths: true,
33 cache: {},
34 packageCache: {}
35}
36
37var EventEmitter = require('events').EventEmitter
38var _hmr = global._hmr = {
39 transformCache: {},
40}
41var hmrPlugin = require('./browserify-hmr')({basedir: SRC_ROOT})
42var cacheModuleData = {}
43
44var globalCache = {}
45watcher.on('all', (event, onPath) => {
46 if (['change', 'unlink', 'unlinkDir'].indexOf(event) === -1) {
47 return
48 }
49 update(onPath)
50})
51var reload = debounce(function () {
52 if (browserSync && typeof browserSync.reload === 'function') {
53 browserSync.reload()
54 }
55}, 300)
56var globalRegExp = /[\\\/]node_modules[\\\/]|[\\\/]src[\\\/]global\.(?:js|libs\.json)$/
57var hmrModuleReg = /\.(jsx?|vue)$/i
58var cacheLibs
59function findAffectedModule(affectsMap, updatedFiles) {
60 if (!Array.isArray(updatedFiles)) {
61 updatedFiles = [updatedFiles]
62 }
63 if (_.every(updatedFiles, f => hmrModuleReg.test(f))) {
64 return updatedFiles
65 }
66 return Array.prototype.concat.apply(
67 updatedFiles.filter(f => hmrModuleReg.test(f)),
68 updatedFiles.filter(f => !hmrModuleReg.test(f)).map(f => affectsMap[f])
69 )
70}
71function update (onPath) {
72 var p = path.resolve(SRC_ROOT, onPath)
73 var hitCache = !!args.cache[p]
74 console.log('hitCache', p, hitCache);
75 var matchGlobal = p.match(globalRegExp)
76 if (matchGlobal) {
77 cacheLibs = void 0
78 } else if (hitCache) {
79 var updatedFiles = [p]
80 var affects = {}
81 var ignoreRegExp = /[\\\/]__hmr_manager.js$|\/brocode\/node_modules\//
82 _.each(args.cache, function (row, id) {
83 _.each(row.deps, function (dep) {
84 var af = affects[dep] || (affects[dep] = [])
85 if (!ignoreRegExp.test(dep) && !ignoreRegExp.test(id)) {
86 af.push(id)
87 }
88 })
89 })
90 var affectedFiles = findAffectedModule(affects, p)
91 delete args.cache[p]
92 delete args.packageCache[p]
93 delete args.packageCache[p + '/package.json']
94 delete args.packageCache[p + '\\package.json']
95 var af = affectedFiles
96 .filter(p => hmrModuleReg.test(p))
97 .filter(p => !(/\/js\/main\//.test(p)))
98 .filter(p => fs.existsSync(p))
99 if (p.indexOf('/js/main/') > -1) {
100 af.push(p)
101 }
102 if (af.length) {
103 log('(update)', af, 'starting...')
104 var start = Date.now()
105 bundle([], af, getOpts(false, true)).then(function () {
106 log('(update)', af, `${Date.now() - start}ms`)
107 })
108 }
109 }
110 reload()
111}
112
113var app = express()
114function log () {
115 var args = Array.prototype.slice.call(arguments)
116 args.unshift((new Date()).toTimeString())
117 console.log.apply(console, args)
118}
119
120app.use(function (req, res, next) {
121 req.serverPort = req.connection.server.address().port
122 next()
123})
124var globalLibsPath = path.join(SRC_ROOT, 'global.libs.json')
125function emitNewModules(socket, moduleData) {
126 if (!socket.moduleData) {
127 return
128 }
129 var currentModuleData = socket.moduleData
130 var newModuleData = _.chain(moduleData)
131 .toPairs()
132 .filter(function(pair) {
133 return pair[1].isNew && (!currentModuleData[pair[0]] || currentModuleData[pair[0]].hash !== pair[1].hash)
134 })
135 .map(function(pair) {
136 return [pair[0], {
137 index: pair[1].index,
138 hash: pair[1].hash,
139 source: pair[1].source,
140 parents: pair[1].parents,
141 deps: pair[1].deps
142 }];
143 })
144 .fromPairs()
145 .value();
146 _.assign(socket.moduleData, newModuleData)
147 var removedModules = _.chain(currentModuleData)
148 .keys()
149 .filter(function(name) {
150 return !has(moduleData, name);
151 })
152 .value();
153 if (Object.keys(newModuleData).length || removedModules.length) {
154 log('[HMR]', 'Emitting updates');
155 socket.emit('new modules', {newModuleData: newModuleData, removedModules: removedModules});
156 }
157}
158function syncModules() {
159 _.each(io.sockets.connected, function(socket) {
160 emitNewModules(socket, cacheModuleData)
161 })
162}
163function getOpts(isGlobal, isHmr) {
164 var opts = { hmr: !!isHmr }
165 var globalLibs = cacheLibs
166 if (!globalLibs) {
167 globalLibs = []
168 try {
169 globalLibs = JSON.parse(fs.readFileSync(globalLibsPath, 'utf-8'))
170 } catch (e) {}
171 cacheLibs = globalLibs
172 }
173 if (isGlobal) {
174 opts.global = true
175 opts.alterb = function (b) {
176 globalLibs.forEach(function (x) {
177 b.require(x[0], {expose: x[1] || x[0]})
178 })
179 }
180 } else {
181 opts.alterb = function (b) {
182 b.on('setNewModuleData', function(moduleData) {
183 _.assign(cacheModuleData, moduleData)
184 syncModules()
185 })
186 }
187 opts.externals = globalLibs.map(function (x) {
188 return x[1] || x[0]
189 }).filter(Boolean)
190 }
191 opts.args = xtend(args, {basedir: SRC_ROOT}, (!isGlobal && isHmr ? {plugin: [hmrPlugin]} : {}))
192 return opts
193}
194app.get(/.*\.js$/i, function (req, res, next) {
195 var isHmr = false
196 if (req.serverPort > app.port + 2) {
197 return next()
198 } else if (req.serverPort === app.port + 2) {
199 isHmr = true
200 }
201 if (!(req.url === '/global.js' || req.url.indexOf('/js/main/') > -1)) {
202 return next()
203 }
204 var filePath = path.join(SRC_ROOT, req.url)
205 var isGlobal = false
206
207 if (req.url === '/global.js') {
208 isGlobal = true
209 }
210 var opts = getOpts(isGlobal, isHmr)
211
212 log('(bundle)', path.sep + path.relative(SRC_ROOT, filePath), `starting...`)
213 var start = Date.now()
214 var b = (exists) => (exists ? bundle([filePath], [], opts) : bundle([], [], opts)).then(b => {
215 log('(bundle)', path.sep + path.relative(SRC_ROOT, filePath), `${Date.now() - start}ms`)
216 res.type('js')
217 res.send(b)
218 })
219
220 if (isGlobal) {
221 fs.exists(filePath, b)
222 } else fs.exists(filePath, function (exists) {
223 if (!exists) {
224 next()
225 return
226 }
227 b(true)
228 })
229})
230
231app.use(function (req, res, next) {
232 var sod = req.serverPort > app.port + 2 ? '/.dist' : '/src'
233 req.url = sod + req.url
234 log('[static]', req.url)
235 next()
236})
237
238app.use(st({
239 cache: false,
240 dot: true,
241 path: APP_ROOT,
242 index: 'index.html'
243}))
244
245var browserSync
246var io
247Object.defineProperty(exports, 'io', {
248 get: function() {
249 return io
250 }
251})
252
253exports.app = app
254exports.start = function (port) {
255 port = port || 8000
256 app.port = port
257 var server = http.createServer(app)
258 var distServer = http.createServer(app)
259 var hmrServer = http.createServer(app)
260 io = socketio(hmrServer)
261 io.on('connection', function(socket) {
262 function log() {
263 console.log.apply(console, [new Date().toTimeString(), '[HMR]'].concat(_.toArray(arguments)));
264 }
265 socket.on('sync', function(syncMsg) {
266 log('User connected, syncing');
267 var oldModuleData = _.pick(cacheModuleData, _.keys(syncMsg))
268 var mainScripts = _.keys(syncMsg).filter(function(name) {
269 return name.startsWith('js/main/')
270 })
271 log('(sync)', mainScripts, 'starting...')
272 var start = Date.now()
273 ;(mainScripts.length
274 ? Promise.all(mainScripts.map((name) => bundle([path.join(SRC_ROOT, name)], [], getOpts(false, true))))
275 : Promise.resolve([])).then(function (results) {
276 log('(sync)', mainScripts, `${Date.now() - start}ms`)
277 socket.moduleData = oldModuleData
278 socket.emit('sync confirm', null);
279 emitNewModules(socket, cacheModuleData)
280 });
281 });
282 });
283
284 server.listen(port, '0.0.0.0', function () {
285 console.log('development server listening at http://0.0.0.0:%d', this.address().port)
286 })
287
288 browserSync = require('browser-sync').create()
289 // 使用 browser-sync
290 browserSync.init({
291 proxy: 'localhost:' + port,
292 port: port + 1,
293 ui: false,
294 open: false
295 }, function() {
296 console.log('development server with browsersync listening at http://0.0.0.0:%d', port + 1)
297 })
298
299 hmrServer.listen(port + 2, '0.0.0.0', function () {
300 console.log('hot module reload server listening at http://0.0.0.0:%d, run `brocode build` to update', this.address().port)
301 })
302
303 distServer.listen(port + 3, '0.0.0.0', function () {
304 console.log('production preview server listening at http://0.0.0.0:%d, run `brocode build` to update', this.address().port)
305 })
306}