diff --git a/docs/logging.md b/docs/logging.md index b7b3b1d74..9b19d94ca 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -63,5 +63,5 @@ var fooLogger = logging.getLogger(__LOGGER__({ label: "foo" })); var barLogger = logging.getLogger(__LOGGER__({ label: "bar" })); ``` -See [react-server-gulp-module-tagger]() for more details on how `__LOGGER__` is +See [module-tagging](/module-tagging) for more details on how `__LOGGER__` is replaced. diff --git a/docs/module-tagging.md b/docs/module-tagging.md new file mode 100644 index 000000000..dbe29b228 --- /dev/null +++ b/docs/module-tagging.md @@ -0,0 +1,26 @@ +# Module tagging + +To enable our [logging](/logging), we have to do a source transform to provide +the file name, and an isomorphic color object, to all of our logging instances. +This keeps coloring consistent for logging across the server and the client, +and makes for a much better developer experience by identifying the file from +which log lines and monitoring originated from on the server and the client +before transpilation and code splitting. + +The easiest way to get started with module tagging is with +[react-server-cli](http://npmjs.com/packages/react-server-cli). If you're +already babelifying your code, you can use +[babel-plugin-react-server](http://npmjs.com/packages/babel-plugin-react-server) +, which is included in +[babel-preset-react-server](http://npmjs.com/packages/babel-preset-react-server) +to tag your react server modules. If you're already using gulp to build your +react server modules, you can use +[react-server-gulp-module-tagger](http://npmjs.com/packages/react-server-gulp-module-tagger) +. If you write custom build scripts, you can use the method that all of the +module taggers use to generate the module tags as well with +[react-server-module-tagger](http://npmjs.com/packages/react-server-module-tagger) +. Because internally all of the module taggers call the same module, it doesn't +matter which method you choose to tag your react server modules; the result is +exactly the same. We provide many methods for tagging your modules to make +react server easy to use (though it also makes testing and development quicker +and easier). diff --git a/packages/babel-plugin-react-server/.babelrc b/packages/babel-plugin-react-server/.babelrc new file mode 100644 index 000000000..eaf32387b --- /dev/null +++ b/packages/babel-plugin-react-server/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "stage-0"] +} diff --git a/packages/babel-plugin-react-server/.gitignore b/packages/babel-plugin-react-server/.gitignore new file mode 100644 index 000000000..45f722230 --- /dev/null +++ b/packages/babel-plugin-react-server/.gitignore @@ -0,0 +1,3 @@ +node_modules +*.log +lib diff --git a/packages/babel-plugin-react-server/.npmignore b/packages/babel-plugin-react-server/.npmignore new file mode 100644 index 000000000..cace0d6dd --- /dev/null +++ b/packages/babel-plugin-react-server/.npmignore @@ -0,0 +1,3 @@ +node_modules +*.log +src diff --git a/packages/babel-plugin-react-server/README.md b/packages/babel-plugin-react-server/README.md new file mode 100644 index 000000000..765ded6e6 --- /dev/null +++ b/packages/babel-plugin-react-server/README.md @@ -0,0 +1,90 @@ +# babel-plugin-react-server + +React Server transpilation + +## Example + +**In** + +```js +var logger = require('react-server').logging.getLogger(__LOGGER__); +``` + +**Out** + +```js +"use strict"; + +var logger = require('react-server').logging.getLogger({ name: 'module.name', color: {} }); +``` + +## Installation + +```sh +$ npm install babel-plugin-react-server +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["react-server"] +} +``` + +### Via CLI + +```sh +$ babel --plugins react-server script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["react-server"] +}); +``` + + +## Configuration + +A fully configured babel plugin in your babelrc would look be + +```json +{ + "plugins": [ + ["react-server", { + "trim": "my-project.components.", + "token": "__LOGGER__" + }] + ] +} +``` + + +### Trim + +A substring to trim off the front of the module name + +```javascript +{ + trim: "my-project.pages." +} +``` + +### Token + +The token to replace in the source code with the module tag. By default, uses +the default logger token `__LOGGER__`, and two future reserved tokens, +`__CHANNEL__` and + +```javascript +{ + token: "__LOGGER__" +} +``` diff --git a/packages/babel-plugin-react-server/package.json b/packages/babel-plugin-react-server/package.json new file mode 100644 index 000000000..84a18e853 --- /dev/null +++ b/packages/babel-plugin-react-server/package.json @@ -0,0 +1,31 @@ +{ + "name": "babel-plugin-react-server", + "version": "0.0.1", + "description": "Babel plugin for React Server transpilation", + "repository": "redfin/babel-plugin-react-server", + "author": "Doug Wade ", + "main": "lib/index.js", + "dependencies": { + "react-server-module-tagger": "^0.0.1" + }, + "devDependencies": { + "babel-core": "^6.3.17", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13", + "mocha": "^2.2.5" + }, + "scripts": { + "clean": "rm -rf lib npm-debug.log*", + "build": "babel src -d lib", + "test": "mocha --compilers js:babel-register", + "test:watch": "npm run test -- --watch", + "prepublish": "npm run clean && npm run build" + }, + "keywords": [ + "react", + "server", + "babel", + "plugin", + "babel-plugin" + ] +} diff --git a/packages/babel-plugin-react-server/src/index.js b/packages/babel-plugin-react-server/src/index.js new file mode 100644 index 000000000..21670b363 --- /dev/null +++ b/packages/babel-plugin-react-server/src/index.js @@ -0,0 +1,34 @@ +import loggerSpec from 'react-server-module-tagger'; +import path from 'path'; + +export default function({types: t }) { + return { + visitor: { + Identifier(p, state) { + const {node} = p; + const {name, type} = node; + + const config = { trim: state.opts.trim }; + const parent = path.resolve(path.join(process.cwd(), '..')) + path.sep; + const fp = this.file.opts.filename.replace(parent, ''); + const file = { path: fp }; + //TODO: Support labels + const moduleTag = loggerSpec.bind({ file, config })(fp); + + let tokens; + if (state.opts.tokens) { + tokens = new Set(state.opts.tokens); + } else { + tokens = new Set(["__LOGGER__", "__CHANNEL__", "__CACHE__"]); + } + + if (tokens.has(name)) { + // this strikes me as a dirty, nasty hack. I think it would be better + // to parse the object as json and coerce it to an array of + // ObjectProperties to construct an ObjectExpression + p.node.name = moduleTag; + } + } + } + }; +} diff --git a/packages/babel-plugin-react-server/test/fixtures/configurable-token/.babelrc b/packages/babel-plugin-react-server/test/fixtures/configurable-token/.babelrc new file mode 100644 index 000000000..b665985c3 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/configurable-token/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + ["../../../src", { "tokens": ["BAR"] }] + ] +} diff --git a/packages/babel-plugin-react-server/test/fixtures/configurable-token/actual.js b/packages/babel-plugin-react-server/test/fixtures/configurable-token/actual.js new file mode 100644 index 000000000..b79db47ce --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/configurable-token/actual.js @@ -0,0 +1 @@ +var logger = require('react-server').logging.getLogger(BAR); diff --git a/packages/babel-plugin-react-server/test/fixtures/configurable-token/expected.js b/packages/babel-plugin-react-server/test/fixtures/configurable-token/expected.js new file mode 100644 index 000000000..9c5c13298 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/configurable-token/expected.js @@ -0,0 +1 @@ +var logger = require('react-server').logging.getLogger({"name":"babel-plugin-react-server.test.fixtures.configurable-token.actual","color":{"server":157,"client":"rgb(127,212,127)"}}); diff --git a/packages/babel-plugin-react-server/test/fixtures/example/.babelrc b/packages/babel-plugin-react-server/test/fixtures/example/.babelrc new file mode 100644 index 000000000..e624888ed --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/example/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + ["../../../src"] + ] +} diff --git a/packages/babel-plugin-react-server/test/fixtures/example/actual.js b/packages/babel-plugin-react-server/test/fixtures/example/actual.js new file mode 100644 index 000000000..861cdc1c7 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/example/actual.js @@ -0,0 +1 @@ +var logger = require('react-server').logging.getLogger(__LOGGER__); diff --git a/packages/babel-plugin-react-server/test/fixtures/example/expected.js b/packages/babel-plugin-react-server/test/fixtures/example/expected.js new file mode 100644 index 000000000..faae3ea8d --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/example/expected.js @@ -0,0 +1 @@ +var logger = require('react-server').logging.getLogger({"name":"babel-plugin-react-server.test.fixtures.example.actual","color":{"server":83,"client":"rgb(42,212,42)"}}); diff --git a/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/.babelrc b/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/.babelrc new file mode 100644 index 000000000..e624888ed --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + ["../../../src"] + ] +} diff --git a/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/actual.js b/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/actual.js new file mode 100644 index 000000000..4d86c7533 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/actual.js @@ -0,0 +1,2 @@ +__CHANNEL__ +__CACHE__ diff --git a/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/expected.js b/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/expected.js new file mode 100644 index 000000000..98ad2e473 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/reserved-future-tokens/expected.js @@ -0,0 +1,2 @@ +{"name":"babel-plugin-react-server.test.fixtures.reserved-future-tokens.actual","color":{"server":203,"client":"rgb(212,42,42)"}}; +{"name":"babel-plugin-react-server.test.fixtures.reserved-future-tokens.actual","color":{"server":203,"client":"rgb(212,42,42)"}}; diff --git a/packages/babel-plugin-react-server/test/fixtures/trim/.babelrc b/packages/babel-plugin-react-server/test/fixtures/trim/.babelrc new file mode 100644 index 000000000..d6b576f6a --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/trim/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + ["../../../src", { "trim": "babel-plugin-react-server.test.fixtures." }] + ] +} diff --git a/packages/babel-plugin-react-server/test/fixtures/trim/actual.js b/packages/babel-plugin-react-server/test/fixtures/trim/actual.js new file mode 100644 index 000000000..861cdc1c7 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/trim/actual.js @@ -0,0 +1 @@ +var logger = require('react-server').logging.getLogger(__LOGGER__); diff --git a/packages/babel-plugin-react-server/test/fixtures/trim/expected.js b/packages/babel-plugin-react-server/test/fixtures/trim/expected.js new file mode 100644 index 000000000..7b16063f2 --- /dev/null +++ b/packages/babel-plugin-react-server/test/fixtures/trim/expected.js @@ -0,0 +1 @@ +var logger = require('react-server').logging.getLogger({"name":"trim.actual","color":{"server":73,"client":"rgb(42,127,127)"}}); diff --git a/packages/babel-plugin-react-server/test/index.js b/packages/babel-plugin-react-server/test/index.js new file mode 100644 index 000000000..9ee8d81b9 --- /dev/null +++ b/packages/babel-plugin-react-server/test/index.js @@ -0,0 +1,26 @@ +import path from 'path'; +import fs from 'fs'; +import assert from 'assert'; +import { transformFileSync } from 'babel-core'; +import plugin from '../src'; + +function trim(str) { + return str.replace(/^\s+|\s+$/, ''); +} + +describe('React Server transpilation', () => { + const fixturesDir = path.join(__dirname, 'fixtures'); + fs.readdirSync(fixturesDir).map((caseName) => { + it(`should ${caseName.split('-').join(' ')}`, () => { + const fixtureDir = path.join(fixturesDir, caseName); + const actualPath = path.join(fixtureDir, 'actual.js'); + const actual = transformFileSync(actualPath).code; + + const expected = fs.readFileSync( + path.join(fixtureDir, 'expected.js') + ).toString(); + + assert.equal(trim(actual), trim(expected)); + }); + }); +}); diff --git a/packages/babel-preset-react-server/index.js b/packages/babel-preset-react-server/index.js index e75605d2f..32ccf6a8f 100644 --- a/packages/babel-preset-react-server/index.js +++ b/packages/babel-preset-react-server/index.js @@ -1,19 +1,11 @@ module.exports = { plugins: [ - require('babel-plugin-transform-es2015-arrow-functions'), - require('babel-plugin-transform-es2015-block-scoping'), - require('babel-plugin-transform-es2015-classes'), - require('babel-plugin-transform-es2015-computed-properties'), - require('babel-plugin-transform-es2015-constants'), - require('babel-plugin-transform-es2015-destructuring'), - require('babel-plugin-transform-es2015-modules-commonjs'), - require('babel-plugin-transform-es2015-parameters'), - require('babel-plugin-transform-es2015-shorthand-properties'), - require('babel-plugin-transform-es2015-spread'), - require('babel-plugin-transform-es2015-template-literals'), - require('babel-plugin-transform-object-rest-spread'), + require('babel-plugin-react-server').default, + require('babel-plugin-transform-runtime'), ], presets: [ + require('babel-preset-es2015'), require('babel-preset-react'), + require('babel-preset-stage-0'), ], } diff --git a/packages/babel-preset-react-server/package.json b/packages/babel-preset-react-server/package.json index 703d9c8d3..f832648f1 100644 --- a/packages/babel-preset-react-server/package.json +++ b/packages/babel-preset-react-server/package.json @@ -18,19 +18,11 @@ "author": "Doug Wade ", "license": "Apache License 2.0", "dependencies": { - "babel-plugin-transform-es2015-arrow-functions": "^6.3.13", - "babel-plugin-transform-es2015-block-scoping": "^6.3.13", - "babel-plugin-transform-es2015-classes": "^6.3.13", - "babel-plugin-transform-es2015-computed-properties": "^6.3.13", - "babel-plugin-transform-es2015-constants": "^6.1.4", - "babel-plugin-transform-es2015-destructuring": "^6.3.13", - "babel-plugin-transform-es2015-modules-commonjs": "^6.3.13", - "babel-plugin-transform-es2015-parameters": "^6.3.13", - "babel-plugin-transform-es2015-shorthand-properties": "^6.3.13", - "babel-plugin-transform-es2015-spread": "^6.3.13", - "babel-plugin-transform-es2015-template-literals": "^6.3.13", - "babel-plugin-transform-object-rest-spread": "^6.3.13", - "babel-preset-react": "^6.5.0" + "babel-plugin-transform-runtime": "^6.9.0", + "babel-plugin-react-server": "^0.0.1", + "babel-preset-es2015": "^6.5.0", + "babel-preset-react": "^6.5.0", + "babel-preset-stage-0": "^6.5.0" }, "devDependencies": { "rimraf": "^2.5.2" diff --git a/packages/generator-react-server/generators/app/index.js b/packages/generator-react-server/generators/app/index.js index 76b401f53..8eefe075c 100644 --- a/packages/generator-react-server/generators/app/index.js +++ b/packages/generator-react-server/generators/app/index.js @@ -43,7 +43,6 @@ module.exports = yeoman.Base.extend({ }); let files = [ - 'gulpfile.js', 'pages/hello-world.js', 'components/hello-world.js', 'package.json', diff --git a/packages/generator-react-server/generators/app/templates/gulpfile.js b/packages/generator-react-server/generators/app/templates/gulpfile.js deleted file mode 100644 index 63224acd2..000000000 --- a/packages/generator-react-server/generators/app/templates/gulpfile.js +++ /dev/null @@ -1,12 +0,0 @@ -const gulp = require('gulp'); -const tagger = require('react-server-gulp-module-tagger'); -const babel = require('gulp-babel'); - -gulp.task('default', () => { - gulp.src(['components/*.js', 'pages/*.js', 'routes.js'], {base: '.'}) - .pipe(tagger()) - .pipe(babel({ - presets: ['react-server'] - })) - .pipe(gulp.dest('build')); -}); diff --git a/packages/generator-react-server/generators/app/templates/package.json b/packages/generator-react-server/generators/app/templates/package.json index 089de11cf..d281952cd 100644 --- a/packages/generator-react-server/generators/app/templates/package.json +++ b/packages/generator-react-server/generators/app/templates/package.json @@ -4,8 +4,7 @@ "description": "A react-server instance", "main": "HelloWorld.js", "scripts": { - "compile": "gulp", - "start": "react-server-cli --port 3010 --js-port 3011 --routes build/routes.js", + "start": "react-server-cli --port 3010 --js-port 3011", "test": "xo && nsp check && ava test.js" }, "license": "Apache-2.0", @@ -22,11 +21,9 @@ }, "devDependencies": { "ava": "^0.15.1", - "babel-preset-react-server": "^0.2.0", + "babel-preset-react-server": "^0.3.0", "eslint-config-xo-react": "^0.7.0", "eslint-plugin-react": "^5.1.1", - "gulp": "^3.9.1", - "gulp-babel": "^6.1.2", "nsp": "^2.3.3", "react-server-gulp-module-tagger": "^0.2.6", "xo": "^0.15.1" diff --git a/packages/generator-react-server/generators/app/templates/test.js b/packages/generator-react-server/generators/app/templates/test.js index 8c49cd7f3..2c4210057 100644 --- a/packages/generator-react-server/generators/app/templates/test.js +++ b/packages/generator-react-server/generators/app/templates/test.js @@ -5,8 +5,6 @@ import test from 'ava'; let rs; test.before('start the server', async () => { - const stdout = await exec('npm run compile'); - console.log(stdout); rs = cp.spawn('npm', ['start']); rs.stderr.on('data', data => console.error(`ERR: ${data}`)); await sleep(10000); @@ -20,20 +18,6 @@ test.after.always('shut down the server', async () => { rs.kill('SIGHUP'); }); -// runs a command asynchronously -function exec(cmd, opts = {maxBuffer: 1024 * 100000}) { - return new Promise((resolve, reject) => { - cp.exec(cmd, opts, (error, stdout, stderr) => { - if (error) { - console.error(stderr); - reject(error); - return; - } - resolve(stdout); - }); - }); -} - // gets the response code for an http request function getResponseCode(url) { return new Promise((resolve, reject) => { diff --git a/packages/react-server-cli/package.json b/packages/react-server-cli/package.json index 81daf15fc..147591f5b 100644 --- a/packages/react-server-cli/package.json +++ b/packages/react-server-cli/package.json @@ -14,6 +14,7 @@ }, "license": "Apache-2.0", "dependencies": { + "babel-preset-react-server": "~0.3.0", "babel-core": "~6.5.1", "babel-loader": "~6.2.2", "babel-runtime": "^6.3.19", diff --git a/packages/react-server-cli/src/compileClient.js b/packages/react-server-cli/src/compileClient.js index 459430905..46d6f7552 100644 --- a/packages/react-server-cli/src/compileClient.js +++ b/packages/react-server-cli/src/compileClient.js @@ -133,6 +133,9 @@ const packageCodeForBrowser = (entrypoints, outputDir, outputUrl, hot, minify, l test: /\.jsx?$/, loader: "babel", exclude: /node_modules/, + query: { + presets: ['babel-preset-react-server'], + }, }, { test: /\.css$/, diff --git a/packages/react-server-gulp-module-tagger/.gitignore b/packages/react-server-gulp-module-tagger/.gitignore new file mode 100644 index 000000000..4a4746b67 --- /dev/null +++ b/packages/react-server-gulp-module-tagger/.gitignore @@ -0,0 +1 @@ +/test/fixtures/**/build/* diff --git a/packages/react-server-gulp-module-tagger/gulpfile.js b/packages/react-server-gulp-module-tagger/gulpfile.js index ecc0412ff..ec91da5d8 100644 --- a/packages/react-server-gulp-module-tagger/gulpfile.js +++ b/packages/react-server-gulp-module-tagger/gulpfile.js @@ -1,8 +1,14 @@ const path = require('path'); const gulp = require('gulp'); +const ava = require('gulp-ava'); const nsp = require('gulp-nsp'); const eslint = require('gulp-eslint'); +gulp.task('ava', () => + gulp.src('test/test.js') + .pipe(ava()) +); + gulp.task('eslint', [], () => gulp.src("index.js") .pipe(eslint()) .pipe(eslint.format()) @@ -11,4 +17,4 @@ gulp.task('eslint', [], () => gulp.src("index.js") gulp.task('nsp', (cb) => nsp({package: path.resolve('package.json')}, cb)); -gulp.task('test', ['nsp', 'eslint']); +gulp.task('test', ['ava', 'nsp', 'eslint']); diff --git a/packages/react-server-gulp-module-tagger/index.js b/packages/react-server-gulp-module-tagger/index.js index 0624449ce..4fa8e573b 100644 --- a/packages/react-server-gulp-module-tagger/index.js +++ b/packages/react-server-gulp-module-tagger/index.js @@ -1,5 +1,6 @@ var replace = require("gulp-replace") , forEach = require("gulp-foreach") +, loggerSpec = require("react-server-module-tagger").default // This pattern matches either of these: // - "__LOGGER__" @@ -9,99 +10,11 @@ var isWindows = ('win32' === process.platform) , THIS_MODULE = isWindows ? /(?:[^\\]+\\node_modules\\)?react-server-gulp-module-tagger\\index\.js$/ : /(?:[^\/]+\/node_modules\/)?react-server-gulp-module-tagger\/index\.js$/ -, BASE_PATH = module.filename.replace(THIS_MODULE,'') module.exports = function(config){ config || (config = {}); + config.basePath = module.filename.replace(THIS_MODULE,''); return forEach(function(stream, file){ return stream.pipe(replace(REPLACE_TOKEN, loggerSpec.bind({file, config}))); }) } - -var loggerSpec = function(fullMatch, optString){ - var fn = this.file.path - , trim = this.config.trim || '' - , opts = {} - - if (fn.indexOf(BASE_PATH) !== 0) { - throw new Error("Unable to handle "+REPLACE_TOKEN+" for "+fn); - } - - if (optString) { - // The slash replacement here is so we don't choke on example - // loggers in comments. - opts = new Function("return "+optString.replace(/^\/\//mg,''))(); // eslint-disable-line no-new-func - } - - opts.name = getName (fn, opts, trim); - opts.color = getColor (fn, opts); - - return JSON.stringify(opts); -} - -var getName = function(fn, opts, trim){ - var slashPattern = isWindows - ?/\\/g - :/\//g - var name = fn.substring(BASE_PATH.length+trim.length, fn.length) - .replace(/\.jsx?$/, '') - .replace(slashPattern,'.') - if (opts.label) { - name += '.'+opts.label - } - return name; -} - -var getColor = (function(){ - - // ANSI escape sequence on the server. - // CSS rgb(...) color in the browser. - var makeColor = function(r,g,b){ - return { - server: 16 + r*36 + g*6 + b, - client: "rgb("+[ - (r*42.5)|0, - (g*42.5)|0, - (b*42.5)|0, - ].join(',')+")", - } - } - - // This produces a list of 24 colors that are distant enough from each - // other to be visually distinct. It's also, conveniently, the same - // palette client side and server side. - var colors = []; - for (var r = 1; r < 6; r+=2) { - for (var g = 1; g < 6; g+=2) { - for (var b = 1; b < 6; b+=2) { - if (r !== g || g !== b) { // No gray. - colors.push(makeColor(r,g,b)); - } - } - } - } - - - // Just want a fairly well distributed deterministic mapping. - // - // Adapted from: - // http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery - var hash = function(str){ - var hash = 0, i, chr, len; - if (str.length === 0) return hash; - for (i = 0, len = str.length; i < len; i++) { - chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; // Convert to 32bit integer - } - - len = colors.length; - - // Positive mod. - return (hash%len+len)%len; - } - - return function(fn){ - return colors[hash(fn)]; - } -})(); diff --git a/packages/react-server-gulp-module-tagger/package.json b/packages/react-server-gulp-module-tagger/package.json index c0048be78..d8d517ae7 100644 --- a/packages/react-server-gulp-module-tagger/package.json +++ b/packages/react-server-gulp-module-tagger/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "gulp test", - "clean": "rimraf target npm-debug.log*" + "clean": "rimraf npm-debug.log* test/fixtures/**/build" }, "repository": "redfin/react-server", "author": "Redfin", @@ -15,9 +15,12 @@ ], "dependencies": { "gulp-foreach": "0.1.0", - "gulp-replace": "0.5.2" + "gulp-replace": "0.5.2", + "react-server-module-tagger": "^0.0.1" }, "devDependencies": { + "ava": "^0.15.2", + "gulp-ava": "^0.12.0", "rimraf": "^2.5.2" } } diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/example/actual.js b/packages/react-server-gulp-module-tagger/test/fixtures/example/actual.js new file mode 100644 index 000000000..397c15fe9 --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/example/actual.js @@ -0,0 +1,3 @@ +var logger = require('react-server').logging.getLogger(__LOGGER__); +var fooLogger = logging.getLogger(__LOGGER__({ label: "foo" })); +var barLogger = logging.getLogger(__LOGGER__({ label: "bar" })); diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/example/expected.js b/packages/react-server-gulp-module-tagger/test/fixtures/example/expected.js new file mode 100644 index 000000000..ca23d1f03 --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/example/expected.js @@ -0,0 +1,3 @@ +var logger = require('react-server').logging.getLogger({"name":"react-server-gulp-module-tagger.test.fixtures.example.actual","color":{"server":217,"client":"rgb(212,127,127)"}}); +var fooLogger = logging.getLogger({"label":"foo","name":"react-server-gulp-module-tagger.test.fixtures.example.actual.foo","color":{"server":217,"client":"rgb(212,127,127)"}}); +var barLogger = logging.getLogger({"label":"bar","name":"react-server-gulp-module-tagger.test.fixtures.example.actual.bar","color":{"server":217,"client":"rgb(212,127,127)"}}); diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/example/gulpfile.js b/packages/react-server-gulp-module-tagger/test/fixtures/example/gulpfile.js new file mode 100644 index 000000000..88048261e --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/example/gulpfile.js @@ -0,0 +1,8 @@ +const gulp = require('gulp'); +const tagger = require('../../..'); + +gulp.task('default', () => { + gulp.src('actual.js') + .pipe(tagger()) + .pipe(gulp.dest('build')); +}); diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/actual.js b/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/actual.js new file mode 100644 index 000000000..4d86c7533 --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/actual.js @@ -0,0 +1,2 @@ +__CHANNEL__ +__CACHE__ diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/expected.js b/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/expected.js new file mode 100644 index 000000000..d6423d80e --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/expected.js @@ -0,0 +1,2 @@ +{"name":"react-server-gulp-module-tagger.test.fixtures.reserved-future-tokens.actual","color":{"server":205,"client":"rgb(212,42,127)"}} +{"name":"react-server-gulp-module-tagger.test.fixtures.reserved-future-tokens.actual","color":{"server":205,"client":"rgb(212,42,127)"}} diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/gulpfile.js b/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/gulpfile.js new file mode 100644 index 000000000..88048261e --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/reserved-future-tokens/gulpfile.js @@ -0,0 +1,8 @@ +const gulp = require('gulp'); +const tagger = require('../../..'); + +gulp.task('default', () => { + gulp.src('actual.js') + .pipe(tagger()) + .pipe(gulp.dest('build')); +}); diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/trim/actual.js b/packages/react-server-gulp-module-tagger/test/fixtures/trim/actual.js new file mode 100644 index 000000000..397c15fe9 --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/trim/actual.js @@ -0,0 +1,3 @@ +var logger = require('react-server').logging.getLogger(__LOGGER__); +var fooLogger = logging.getLogger(__LOGGER__({ label: "foo" })); +var barLogger = logging.getLogger(__LOGGER__({ label: "bar" })); diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/trim/expected.js b/packages/react-server-gulp-module-tagger/test/fixtures/trim/expected.js new file mode 100644 index 000000000..fb0047b2a --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/trim/expected.js @@ -0,0 +1,3 @@ +var logger = require('react-server').logging.getLogger({"name":"fixtures.trim.actual","color":{"server":147,"client":"rgb(127,127,212)"}}); +var fooLogger = logging.getLogger({"label":"foo","name":"fixtures.trim.actual.foo","color":{"server":147,"client":"rgb(127,127,212)"}}); +var barLogger = logging.getLogger({"label":"bar","name":"fixtures.trim.actual.bar","color":{"server":147,"client":"rgb(127,127,212)"}}); diff --git a/packages/react-server-gulp-module-tagger/test/fixtures/trim/gulpfile.js b/packages/react-server-gulp-module-tagger/test/fixtures/trim/gulpfile.js new file mode 100644 index 000000000..9339d0d6e --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/fixtures/trim/gulpfile.js @@ -0,0 +1,8 @@ +const gulp = require('gulp'); +const tagger = require('../../..'); + +gulp.task('default', () => { + gulp.src('actual.js') + .pipe(tagger({ trim: 'react-server-gulp-module-tagger.test.' })) + .pipe(gulp.dest('build')); +}); diff --git a/packages/react-server-gulp-module-tagger/test/test.js b/packages/react-server-gulp-module-tagger/test/test.js new file mode 100644 index 000000000..bd3fed3fb --- /dev/null +++ b/packages/react-server-gulp-module-tagger/test/test.js @@ -0,0 +1,53 @@ +import test from 'ava'; +import fs from 'fs'; +import cp from 'child_process'; +import path from 'path'; + +getTestCases().then((testCases) => { + testCases.forEach((dir) => { + test(`testing fixture in ${dir}`, async t => { + await runGulp(dir); + const expected = await readFile(path.join('fixtures', dir, 'expected.js')); + const actual = await readFile(path.join('fixtures', dir, 'build', 'actual.js')); + t.is(actual.toString(), expected.toString()); + }); + }); +}); + +function getTestCases() { + return new Promise((resolve, reject) => { + fs.readdir('fixtures', (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }) + }) +} + +function readFile(filename) { + return new Promise((resolve, reject) => { + fs.readFile(filename, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); +} + +function runGulp(dir) { + return new Promise((resolve, reject) => { + dir = path.join(__dirname, 'fixtures', dir); + cp.exec('gulp', { cwd: dir }, (err, stdout, stderr) => { + if (err) { + console.error(stderr); + reject(err); + } else { + resolve(stdout); + } + }); + }); +} diff --git a/packages/react-server-module-tagger/.gitignore b/packages/react-server-module-tagger/.gitignore new file mode 100644 index 000000000..012a3cd68 --- /dev/null +++ b/packages/react-server-module-tagger/.gitignore @@ -0,0 +1 @@ +index.js diff --git a/packages/react-server-module-tagger/.npmignore b/packages/react-server-module-tagger/.npmignore new file mode 100644 index 000000000..13a9c1b4b --- /dev/null +++ b/packages/react-server-module-tagger/.npmignore @@ -0,0 +1 @@ +index.babel.js diff --git a/packages/react-server-module-tagger/README.md b/packages/react-server-module-tagger/README.md new file mode 100644 index 000000000..ee662c30e --- /dev/null +++ b/packages/react-server-module-tagger/README.md @@ -0,0 +1,25 @@ +# react-server-module-tagger + +A function for tagging [react-server](https://www.npmjs.com/package/react-server) +logger instances with information about the module they're being used in. + +To transpile your source for use with +[React Server](https://www.npmjs.com/package/react-server), install gulp and the plugin + +```shell +npm i -D gulp react-server-module-tagger +``` + +Then require and call the function. The tagger expects to have config and file +data on its prototype, so use `.bind`. + +```javascript +const tagger = require('react-server-module-tagger'); +const filepath = 'path/to/my/output.js'; +const optString = '({label:"foo"})' +const moduleTag = tagger.bind({ file: { path: filepath }, config: { trim: 'path/to'} })(filepath, optString)); +``` + +returns a logger instance that will have consistent coloring on the server and +the client, and that has a human-friendly, readable name that easily maps to +the file tree (in this example `components.my-feature.foo`). diff --git a/packages/react-server-module-tagger/index.babel.js b/packages/react-server-module-tagger/index.babel.js new file mode 100644 index 000000000..2efc18b1e --- /dev/null +++ b/packages/react-server-module-tagger/index.babel.js @@ -0,0 +1,129 @@ +var isWindows = ('win32' === process.platform); + +/** + * A util function for tagging modules. Expects to find data on its prototype + * as follows + * { + * file: { + * path: the path to the file to tag + * } + * config: { + * trim: the string to trim off the front of the logger name + * } + * } + * @example + * const file = '/path/to/my/file.js'; + * loggerSpec.bind({file, { trim: 'path.to.' }})(file, '({label: "foo"})') + * // returns '{\"label\":\"foo\",\"name\":\"my.file\",\"color\":{\"server\":87,\"client\":\"rgb(42,212,212)\"}}' + * @param {String} fullMatch May also be provided as this.file.path, the path to the file + * @param {String} optString The label to add to the module tag, in the form '({label:"$label"})' + * @return {String} A json object containing a module identifier + */ +export default function(fullMatch, optString){ + var fn = this.file && this.file.path ? this.file.path : fullMatch + , trim = this.config.trim || '' + , basePath = this.config.basePath || '' + , opts = {} + + if (fn.indexOf(basePath) !== 0) { + throw new Error("Unable to handle " + basePath + " for " + fn); + } + + if (optString) { + // The slash replacement here is so we don't choke on example + // loggers in comments. + opts = new Function("return "+optString.replace(/^\/\//mg,''))(); // eslint-disable-line no-new-func + } + + opts.name = getName (fn, opts, trim, basePath); + opts.color = getColor (fn, opts); + + return JSON.stringify(opts); +} + +/** + * Gets the name of a logger from its filepath. + * @example + * getName( + * 'my-component', + * { label: 'sub' }, + * 'my-project.src', + * 'my-project/src/components/my-component.js' + * ) // returns "components.my-component" + * @param {String} fn filename + * @param {Object} opts { label: 'Optional logger label' } + * @param {String} trim The leading portion of the name to remove. + * @param {String} basePath The path to the file + * @return {String} The logger name, e.g. my-project.components.my-component + */ +var getName = function(fn, opts, trim, basePath){ + var slashPattern = isWindows + ?/\\/g + :/\//g + var name = fn.substring(basePath.length+trim.length, fn.length) + .replace(/\.jsx?$/, '') + .replace(slashPattern,'.') + if (opts.label) { + name += '.'+opts.label + } + return name; +} + +/** + * Gets isomorphic color objects from filenames. + * @param {String} filename + * @return {Object} An isomorphic color object in the form {"color":{"server":147,"client":"rgb(127,127,212)"}} + */ +var getColor = (function(){ + + // ANSI escape sequence on the server. + // CSS rgb(...) color in the browser. + var makeColor = function(r,g,b){ + return { + server: 16 + r*36 + g*6 + b, + client: "rgb("+[ + (r*42.5)|0, + (g*42.5)|0, + (b*42.5)|0, + ].join(',')+")", + } + } + + // This produces a list of 24 colors that are distant enough from each + // other to be visually distinct. It's also, conveniently, the same + // palette client side and server side. + var colors = []; + for (var r = 1; r < 6; r+=2) { + for (var g = 1; g < 6; g+=2) { + for (var b = 1; b < 6; b+=2) { + if (r !== g || g !== b) { // No gray. + colors.push(makeColor(r,g,b)); + } + } + } + } + + + // Just want a fairly well distributed deterministic mapping. + // + // Adapted from: + // http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery + var hash = function(str){ + var hash = 0, i, chr, len; + if (str.length === 0) return hash; + for (i = 0, len = str.length; i < len; i++) { + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + + len = colors.length; + + // Positive mod. + return (hash%len+len)%len; + } + + return function(fn){ + return colors[hash(fn)]; + } +})(); diff --git a/packages/react-server-module-tagger/package.json b/packages/react-server-module-tagger/package.json new file mode 100644 index 000000000..4b6627a2a --- /dev/null +++ b/packages/react-server-module-tagger/package.json @@ -0,0 +1,32 @@ +{ + "name": "react-server-module-tagger", + "version": "0.0.1", + "description": "calculates module level config", + "main": "index.js", + "scripts": { + "test": "ava test.js", + "compile": "babel --presets es2015,stage-0 -o index.js index.babel.js", + "clean": "del index.js npm-debug.log*", + "prepublish": "npm run compile" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/redfin/react-server.git" + }, + "keywords": [ + "react-server" + ], + "author": "Doug Wade ", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/redfin/react-server/issues" + }, + "homepage": "https://github.com/redfin/react-server#readme", + "devDependencies": { + "ava": "^0.15.2", + "babel-cli": "^6.9.0", + "babel-preset-es2015": "^6.9.0", + "babel-preset-stage-0": "^6.5.0", + "del-cli": "^0.2.0" + } +} diff --git a/packages/react-server-module-tagger/test.js b/packages/react-server-module-tagger/test.js new file mode 100644 index 000000000..9d27935ea --- /dev/null +++ b/packages/react-server-module-tagger/test.js @@ -0,0 +1,32 @@ +import test from 'ava'; +import loggerSpec from '.'; + +test('creates a module tag', t => { + const expected = '{\"name\":\"foo.bar\",\"color\":{\"server\":135,\"client\":\"rgb(127,42,212)\"}}'; + + const file = 'foo/bar'; + const config = {}; + const actual = loggerSpec.bind({file, config})(file); + + t.is(expected, actual); +}); + +test('trims prefix from module tag name', t => { + const expected = '{\"name\":\"quux\",\"color\":{\"server\":143,\"client\":\"rgb(127,127,42)\"}}'; + + const file = 'baz/quux'; + const config = { trim: 'baz.' }; + const actual = loggerSpec.bind({file, config})(file); + + t.is(expected, actual); +}); + +test('adds labels', t => { + const expected = '{\"label\":\"foo\",\"name\":\"has.label.foo\",\"color\":{\"server\":87,\"client\":\"rgb(42,212,212)\"}}'; + + const file = 'has/label'; + const config = {}; + const actual = loggerSpec.bind({file, config})(file, '({label: "foo"})'); + + t.is(expected, actual); +});