UNPKG

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