From e419ec43fc3526c0580c871ad53c311bd778e430 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 8 Feb 2016 11:28:51 -0800 Subject: [PATCH] Make render-server able to load TS/ES2015 without precompilation --- .../MusicStore/ReactApp/fx/render-server.js | 40 ++++++----------- .../ReactApp/fx/require-ts-babel.js | 44 +++++++++++++++++++ samples/react/MusicStore/package.json | 1 + 3 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 samples/react/MusicStore/ReactApp/fx/require-ts-babel.js diff --git a/samples/react/MusicStore/ReactApp/fx/render-server.js b/samples/react/MusicStore/ReactApp/fx/render-server.js index c5971b8..7299c68 100644 --- a/samples/react/MusicStore/ReactApp/fx/render-server.js +++ b/samples/react/MusicStore/ReactApp/fx/render-server.js @@ -1,30 +1,16 @@ +require('./require-ts-babel')(); // Enable loading TS/TSX/JSX/ES2015 modules var url = require('url'); -var babelCore = require('babel-core'); -var babelConfig = { - presets: ["es2015", "react"] -}; +var domainTasks = require('./domain-tasks.ts'); -var origJsLoader = require.extensions['.js']; -require.extensions['.js'] = loadViaBabel; -require.extensions['.jsx'] = loadViaBabel; - -function loadViaBabel(module, filename) { - // Assume that all the app's own code is ES2015+ (optionally with JSX), but that none of the node_modules are. - // The distinction is important because ES2015+ forces strict mode, and it may break ES3/5 if you try to run it in strict - // mode when the developer didn't expect that (e.g., current versions of underscore.js can't be loaded in strict mode). - var useBabel = filename.indexOf('node_modules') < 0; - if (useBabel) { - var transformedFile = babelCore.transformFileSync(filename, babelConfig); - return module._compile(transformedFile.code, filename); - } else { - return origJsLoader.apply(this, arguments); +function render(bootModulePath, requestUrl, callback) { + var bootFunc = require(bootModulePath); + if (typeof bootFunc !== 'function') { + bootFunc = bootFunc.default; } -} - -var domainTasks = require('./domain-tasks.js'); -var bootServer = require('../boot-server.jsx').default; - -function render(requestUrl, callback) { + if (typeof bootFunc !== 'function') { + throw new Error('The module at ' + bootModulePath + ' must export a default function, otherwise we don\'t know how to invoke it.') + } + var params = { location: url.parse(requestUrl), url: requestUrl, @@ -36,7 +22,7 @@ function render(requestUrl, callback) { // Since route matching is asynchronous, add the rendering itself to the list of tasks we're awaiting domainTasks.addTask(new Promise(function (resolve, reject) { // Now actually perform the first render that will match a route and commence associated tasks - bootServer(params, function(error, result) { + bootFunc(params, function(error, result) { if (error) { reject(error); } else { @@ -51,7 +37,7 @@ function render(requestUrl, callback) { // By now, all the data should be loaded, so we can render for real based on the state now // TODO: Add an optimisation where, if domain-tasks had no outstanding tasks at the end of // the previous render, we don't re-render (we can use the previous html and state). - bootServer(params, callback); + bootFunc(params, callback); }).catch(function(error) { process.nextTick(() => { // Because otherwise you can't throw from inside a catch callback(error, null); @@ -59,7 +45,7 @@ function render(requestUrl, callback) { }); } -render('/', (err, html) => { +render('../boot-server.tsx', '/', (err, html) => { if (err) { throw err; } diff --git a/samples/react/MusicStore/ReactApp/fx/require-ts-babel.js b/samples/react/MusicStore/ReactApp/fx/require-ts-babel.js new file mode 100644 index 0000000..8656138 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/fx/require-ts-babel.js @@ -0,0 +1,44 @@ +var fs = require('fs'); +var ts = require('ntypescript'); +var babelCore = require('babel-core'); +var resolveBabelRc = require('babel-loader/lib/resolve-rc'); // If this ever breaks, we can easily scan up the directory hierarchy ourselves +var origJsLoader = require.extensions['.js']; + +function resolveBabelOptions(relativeToFilename) { + var babelRcText = resolveBabelRc(relativeToFilename); + return babelRcText ? JSON.parse(babelRcText) : {}; +} + +function loadViaTypeScript(module, filename) { + // First perform a minimal transpilation from TS code to ES2015. This is very fast (doesn't involve type checking) + // and is unlikely to need any special compiler options + var src = fs.readFileSync(filename, 'utf8'); + var compilerOptions = { jsx: ts.JsxEmit.Preserve, module: ts.ModuleKind.ES2015, target: ts.ScriptTarget.ES6 }; + var es6Code = ts.transpile(src, compilerOptions, 'test.tsx', /* diagnostics */ []); + + // Second, process the ES2015 via Babel. We have to do this (instead of going directly from TS to ES5) because + // TypeScript's ES5 output isn't exactly compatible with Node-style CommonJS modules. The main issue is with + // resolving default exports - https://github.com/Microsoft/TypeScript/issues/2719 + var es5Code = babelCore.transform(es6Code, resolveBabelOptions(filename)).code; + return module._compile(es5Code, filename); +} + +function loadViaBabel(module, filename) { + // Assume that all the app's own code is ES2015+ (optionally with JSX), but that none of the node_modules are. + // The distinction is important because ES2015+ forces strict mode, and it may break ES3/5 if you try to run it in strict + // mode when the developer didn't expect that (e.g., current versions of underscore.js can't be loaded in strict mode). + var useBabel = filename.indexOf('node_modules') < 0; + if (useBabel) { + var transformedFile = babelCore.transformFileSync(filename, resolveBabelOptions(filename)); + return module._compile(transformedFile.code, filename); + } else { + return origJsLoader.apply(this, arguments); + } +} + +module.exports = function register() { + require.extensions['.js'] = loadViaBabel; + require.extensions['.jsx'] = loadViaBabel; + require.extensions['.ts'] = loadViaTypeScript; + require.extensions['.tsx'] = loadViaTypeScript; +} diff --git a/samples/react/MusicStore/package.json b/samples/react/MusicStore/package.json index 16599f6..c56e854 100644 --- a/samples/react/MusicStore/package.json +++ b/samples/react/MusicStore/package.json @@ -22,6 +22,7 @@ "bootstrap": "^3.3.6", "domain-context": "^0.5.1", "isomorphic-fetch": "^2.2.1", + "ntypescript": "^1.201602072208.1", "react": "^0.14.7", "react-bootstrap": "^0.28.2", "react-dom": "^0.14.7",