From 64a01dff71edc77b7b12acbd2b42973842d9bd74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Ramos?= Date: Sun, 23 Oct 2016 05:27:18 +0100 Subject: [PATCH] bootstrap ui framework --- ui/.babelrc | 16 +++ ui/.eslintignore | 3 + ui/.eslintrc | 29 +++++ ui/.tern-project | 15 +++ ui/README.md | 1 + ui/docs/components.js | 0 ui/docs/index.js | 17 +++ ui/docs/root.js | 16 +++ ui/package.json | 67 ++++++++++++ ui/scripts/build-docs-static.sh | 3 + ui/scripts/clean-static.sh | 0 ui/src/base/index.js | 5 + ui/src/button/index.js | 16 +++ ui/src/button/readme.md | 29 +++++ ui/src/button/style.css | 4 + ui/src/container/index.js | 5 + ui/src/docs.js | 3 + ui/src/index.js | 5 + ui/static/.gitignore | 3 + ui/static/theme.css | 72 +++++++++++++ ui/test/index.js | 25 +++++ ui/webpack/config.js | 76 +++++++++++++ ui/webpack/development.js | 35 ++++++ ui/webpack/embed-markdown-loader/package.json | 28 +++++ .../src/compile/compile.js | 100 ++++++++++++++++++ .../src/compile/detach.js | 51 +++++++++ .../src/compile/detached.js | 25 +++++ .../src/compile/index.js | 18 ++++ ui/webpack/embed-markdown-loader/src/eval.js | 29 +++++ .../embed-markdown-loader/src/highlight.js | 15 +++ ui/webpack/embed-markdown-loader/src/index.js | 28 +++++ ui/webpack/embed-markdown-loader/src/parse.js | 98 +++++++++++++++++ ui/webpack/entrypoints.js | 21 ++++ ui/webpack/index.js | 2 + ui/webpack/production.js | 31 ++++++ 35 files changed, 891 insertions(+) create mode 100644 ui/.babelrc create mode 100644 ui/.eslintignore create mode 100644 ui/.eslintrc create mode 100644 ui/.tern-project create mode 100644 ui/README.md create mode 100644 ui/docs/components.js create mode 100644 ui/docs/index.js create mode 100644 ui/docs/root.js create mode 100644 ui/package.json create mode 100644 ui/scripts/build-docs-static.sh create mode 100644 ui/scripts/clean-static.sh create mode 100644 ui/src/base/index.js create mode 100644 ui/src/button/index.js create mode 100644 ui/src/button/readme.md create mode 100644 ui/src/button/style.css create mode 100644 ui/src/container/index.js create mode 100644 ui/src/docs.js create mode 100644 ui/src/index.js create mode 100644 ui/static/.gitignore create mode 100644 ui/static/theme.css create mode 100644 ui/test/index.js create mode 100644 ui/webpack/config.js create mode 100644 ui/webpack/development.js create mode 100644 ui/webpack/embed-markdown-loader/package.json create mode 100644 ui/webpack/embed-markdown-loader/src/compile/compile.js create mode 100644 ui/webpack/embed-markdown-loader/src/compile/detach.js create mode 100644 ui/webpack/embed-markdown-loader/src/compile/detached.js create mode 100644 ui/webpack/embed-markdown-loader/src/compile/index.js create mode 100644 ui/webpack/embed-markdown-loader/src/eval.js create mode 100644 ui/webpack/embed-markdown-loader/src/highlight.js create mode 100644 ui/webpack/embed-markdown-loader/src/index.js create mode 100644 ui/webpack/embed-markdown-loader/src/parse.js create mode 100644 ui/webpack/entrypoints.js create mode 100644 ui/webpack/index.js create mode 100644 ui/webpack/production.js diff --git a/ui/.babelrc b/ui/.babelrc new file mode 100644 index 00000000..3de5edda --- /dev/null +++ b/ui/.babelrc @@ -0,0 +1,16 @@ +{ + "sourceMaps": "both", + "presets": [ + "react", + "es2015" + ], + "plugins": [ + "react-hot-loader/babel", + "add-module-exports", + "transform-exponentiation-operator", + "syntax-async-functions", + ["transform-object-rest-spread", { + "useBuiltIns": true + }] + ] +} diff --git a/ui/.eslintignore b/ui/.eslintignore new file mode 100644 index 00000000..b1d4c644 --- /dev/null +++ b/ui/.eslintignore @@ -0,0 +1,3 @@ +/node_modules +coverage +.nyc_output \ No newline at end of file diff --git a/ui/.eslintrc b/ui/.eslintrc new file mode 100644 index 00000000..55e4244d --- /dev/null +++ b/ui/.eslintrc @@ -0,0 +1,29 @@ +{ + "extends": "semistandard", + "parser": "babel-eslint", + "parserOptions": { + "ecmaVersion": 7, + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "module" + }, + "plugins": [ + "babel", + "react" + ], + "rules": { + "generator-star-spacing": 0, + "babel/generator-star-spacing": 1, + "space-before-function-paren": [2, "never"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2, + "object-curly-newline": ["error", { + "minProperties": 1 + }], + "sort-vars": ["error", { + "ignoreCase": true + }] + } +} diff --git a/ui/.tern-project b/ui/.tern-project new file mode 100644 index 00000000..22bab84c --- /dev/null +++ b/ui/.tern-project @@ -0,0 +1,15 @@ +{ + "libs": [ + "ecmascript", + "browser" + ], + "plugins": { + "doc_comment": true, + "local-scope": true, + "jsx": true, + "node": true, + "webpack": { + "configPath": "./webpack/index.js" + } + } +} \ No newline at end of file diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 00000000..bb78c652 --- /dev/null +++ b/ui/README.md @@ -0,0 +1 @@ +# Joyent Dashboard UI Framework diff --git a/ui/docs/components.js b/ui/docs/components.js new file mode 100644 index 00000000..e69de29b diff --git a/ui/docs/index.js b/ui/docs/index.js new file mode 100644 index 00000000..af547af9 --- /dev/null +++ b/ui/docs/index.js @@ -0,0 +1,17 @@ +const ReactDOM = require('react-dom'); +const React = require('react'); + +const render = () => { + const Root = require('./root'); + + ReactDOM.render( + , + document.getElementById('root') + ); +}; + +render(); + +if (module.hot) { + module.hot.accept('./root', render); +} diff --git a/ui/docs/root.js b/ui/docs/root.js new file mode 100644 index 00000000..10bb9624 --- /dev/null +++ b/ui/docs/root.js @@ -0,0 +1,16 @@ +const React = require('react'); +const ReactHotLoader = require('react-hot-loader'); +const Button = require('../src/button/readme.md'); +const InnerHTML = require('dangerously-set-inner-html'); + +const { + AppContainer +} = ReactHotLoader; + +module.exports = () => { + return ( + + + + ); +}; diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 00000000..dc34b61a --- /dev/null +++ b/ui/package.json @@ -0,0 +1,67 @@ +{ + "name": "joyent-dashboard-frontend", + "version": "1.0.0", + "private": true, + "license": "private", + "scripts": { + "start": "webpack-dev-server --open --config webpack/index.js", + "lint": "eslint .", + "test": "NODE_ENV=test nyc ava test/*.js --verbose", + "build": "NODE_ENV=production webpack --config webpack/index.js", + "clean-static": "sh scripts/clean-static.sh", + "build-docs-static": "sh scripts/build-docs-static.sh" + }, + "dependencies": { + "react": "^15.3.2" + }, + "devDependencies": { + "ava": "^0.16.0", + "babel-core": "^6.17.0", + "babel-eslint": "^7.0.0", + "babel-loader": "^6.2.5", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-syntax-async-functions": "^6.13.0", + "babel-plugin-transform-object-rest-spread": "^6.16.0", + "babel-preset-es2015": "^6.16.0", + "babel-preset-react": "^6.16.0", + "babel-register": "^6.16.3", + "css-loader": "^0.25.0", + "dangerously-set-inner-html": "^1.0.0", + "enzyme": "^2.5.1", + "eslint": "^3.8.1", + "eslint-config-semistandard": "^7.0.0", + "eslint-config-standard": "^6.2.0", + "eslint-plugin-babel": "^3.3.0", + "eslint-plugin-promise": "^3.3.0", + "eslint-plugin-react": "^6.4.1", + "eslint-plugin-standard": "^2.0.1", + "extract-text-webpack-plugin": "^2.0.0-beta.4", + "html-loader": "^0.4.4", + "json-loader": "^0.5.4", + "nyc": "^8.3.1", + "postcss-cssnext": "^2.8.0", + "postcss-loader": "^1.0.0", + "pre-commit": "^1.1.3", + "react-addons-test-utils": "^15.3.2", + "react-dom": "^15.3.2", + "react-hot-loader": "^3.0.0-beta.6", + "react-router": "^4.0.0-alpha.4", + "style-loader": "^0.13.1", + "webpack": "^2.1.0-beta.25", + "webpack-dev-server": "^1.16.2", + "webpack-shell-plugin": "^0.4.3" + }, + "ava": { + "failFast": true, + "cache": false, + "require": [ + "babel-register" + ], + "babel": "inherit" + }, + "pre-commit": [ + "lint", + "test", + "coverage" + ] +} diff --git a/ui/scripts/build-docs-static.sh b/ui/scripts/build-docs-static.sh new file mode 100644 index 00000000..09b0fb45 --- /dev/null +++ b/ui/scripts/build-docs-static.sh @@ -0,0 +1,3 @@ +echo $(pwd) +# cp -r ../static/* ../docs/static/ +# mv ../docs/static/index.html ../docs/ \ No newline at end of file diff --git a/ui/scripts/clean-static.sh b/ui/scripts/clean-static.sh new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/base/index.js b/ui/src/base/index.js new file mode 100644 index 00000000..32dd9ac6 --- /dev/null +++ b/ui/src/base/index.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => { + return null; +}; diff --git a/ui/src/button/index.js b/ui/src/button/index.js new file mode 100644 index 00000000..d53e09a0 --- /dev/null +++ b/ui/src/button/index.js @@ -0,0 +1,16 @@ +const React = require('react'); +const styles = require('./style.css'); + +module.exports = ({ + disabled = false, + children +}) => { + return ( + + ); +}; diff --git a/ui/src/button/readme.md b/ui/src/button/readme.md new file mode 100644 index 00000000..5cd85730 --- /dev/null +++ b/ui/src/button/readme.md @@ -0,0 +1,29 @@ +# ` +); +``` + +## usage + +```js +const React = require('react'); +const Button = require('ui/button'); + +module.exports = () => { + return ( + + ); +} +``` diff --git a/ui/src/button/style.css b/ui/src/button/style.css new file mode 100644 index 00000000..2e38a505 --- /dev/null +++ b/ui/src/button/style.css @@ -0,0 +1,4 @@ +.button { + background-color: #e500ee; + margin: none; +} \ No newline at end of file diff --git a/ui/src/container/index.js b/ui/src/container/index.js new file mode 100644 index 00000000..32dd9ac6 --- /dev/null +++ b/ui/src/container/index.js @@ -0,0 +1,5 @@ +const React = require('react'); + +module.exports = () => { + return null; +}; diff --git a/ui/src/docs.js b/ui/src/docs.js new file mode 100644 index 00000000..029813ac --- /dev/null +++ b/ui/src/docs.js @@ -0,0 +1,3 @@ +module.exports = { + Button: require('./button/readme.md') +}; diff --git a/ui/src/index.js b/ui/src/index.js new file mode 100644 index 00000000..a405120e --- /dev/null +++ b/ui/src/index.js @@ -0,0 +1,5 @@ +module.exports = { + Button: require('./button'), + Container: require('./container'), + Base: require('./base') +}; diff --git a/ui/static/.gitignore b/ui/static/.gitignore new file mode 100644 index 00000000..60687768 --- /dev/null +++ b/ui/static/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!theme.css diff --git a/ui/static/theme.css b/ui/static/theme.css new file mode 100644 index 00000000..41dadc79 --- /dev/null +++ b/ui/static/theme.css @@ -0,0 +1,72 @@ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #8e908c; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #c82829; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f5871f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #eab700; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #718c00; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #4271ae; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8959a8; +} + +.hljs { + display: block; + overflow-x: auto; + background: white; + color: #4d4d4c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} \ No newline at end of file diff --git a/ui/test/index.js b/ui/test/index.js new file mode 100644 index 00000000..adde0c48 --- /dev/null +++ b/ui/test/index.js @@ -0,0 +1,25 @@ +const test = require('ava'); +const enzyme = require('enzyme'); +const React = require('react'); + +const { + shallow +} = enzyme; + +test('renders without exploding', (t) => { + const App = require('../src/containers/app'); + const wrapper = shallow(); + t.deepEqual(wrapper.length, 1); +}); + +test('renders without exploding', (t) => { + const Home = require('../src/containers/home'); + const wrapper = shallow(); + t.deepEqual(wrapper.length, 1); +}); + +test('renders without exploding', (t) => { + const NotFound = require('../src/containers/not-found'); + const wrapper = shallow(); + t.deepEqual(wrapper.length, 1); +}); diff --git a/ui/webpack/config.js b/ui/webpack/config.js new file mode 100644 index 00000000..7df95a1b --- /dev/null +++ b/ui/webpack/config.js @@ -0,0 +1,76 @@ +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const webpack = require('webpack'); +const path = require('path'); + +module.exports = { + context: path.join(__dirname, '../'), + output: { + path: path.join(__dirname, '../static'), + publicPath: '/static/', + filename: '[name].js' + }, + plugins: [ + new webpack.NoErrorsPlugin(), + new ExtractTextPlugin({ + filename: '[name].css', + allChunks: true + }), + new webpack.LoaderOptionsPlugin({ + options: { + postcss: { + plugins: () => { + return [ + require('postcss-cssnext') + ]; + } + }, + 'embed-markdown-loader': { + // webpackConfigFullpath: path.join(__dirname, 'index.js') don't detach yet (has a bug in the production config) + } + } + }) + ], + resolveLoader: { + alias: { + 'embed-markdown-loader': path.join(__dirname, './embed-markdown-loader') + } + }, + module: { + loaders: [{ + test: /js?$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, '../src'), + path.join(__dirname, '../docs') + ], + loader: 'babel' + }, { + test: /\.json?$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, '../src'), + path.join(__dirname, '../docs') + ], + loader: 'json' + }, { + test: /\.md?$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, '../src'), + path.join(__dirname, '../docs') + ], + loader: 'html-loader!embed-markdown-loader' + }, { + test: /\.css?$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, '../src'), + path.join(__dirname, '../docs') + ], + loader: ExtractTextPlugin.extract({ + fallbackLoader: 'style-loader', + loader: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader' + }) + }] + } +}; diff --git a/ui/webpack/development.js b/ui/webpack/development.js new file mode 100644 index 00000000..457c6c15 --- /dev/null +++ b/ui/webpack/development.js @@ -0,0 +1,35 @@ +const graphql = require('../../cloudapi-graphql/src/endpoint'); +const config = require('./config.js'); +const entries = require('./entrypoints'); +const webpack = require('webpack'); + +const devServer = { + hot: true, + compress: true, + lazy: false, + publicPath: '/static/', + setup: (app) => { + app.use('/graphql', graphql); + }, + historyApiFallback: { + index: './static/index.html' + } +}; + +module.exports = Object.assign(config, { + entry: entries.reduce((all, entry) => { + all[entry.name] = [ + 'react-hot-loader/patch', + 'webpack-dev-server/client?http://localhost:8080', + 'webpack/hot/only-dev-server', + entry.path + ]; + + return all; + }, {}), + plugins: config.plugins.concat([ + new webpack.HotModuleReplacementPlugin() + ]), + devtool: 'source-map', + devServer +}); diff --git a/ui/webpack/embed-markdown-loader/package.json b/ui/webpack/embed-markdown-loader/package.json new file mode 100644 index 00000000..66bf0f69 --- /dev/null +++ b/ui/webpack/embed-markdown-loader/package.json @@ -0,0 +1,28 @@ +{ + "name": "remarkable-loader", + "version": "1.0.0", + "main": "src/index.js", + "scripts": { + "lint": "eslint ." + }, + "dependencies": { + "async": "^2.1.2", + "clone": "^2.0.0", + "get-stdin": "^5.0.1", + "highlight.js": "^9.7.0", + "loader-utils": "^0.2.16", + "lodash.clonedeep": "^4.5.0", + "memory-fs": "^0.3.0", + "minimist": "^1.2.0", + "remarkable": "^1.7.1", + "uuid": "^2.0.3", + "webpack": "^2.1.0-beta.25" + }, + "devDependencies": { + "eslint": "^3.8.1", + "eslint-config-semistandard": "^7.0.0", + "eslint-config-standard": "^6.2.0", + "eslint-plugin-promise": "^3.3.0", + "eslint-plugin-standard": "^2.0.1" + } +} diff --git a/ui/webpack/embed-markdown-loader/src/compile/compile.js b/ui/webpack/embed-markdown-loader/src/compile/compile.js new file mode 100644 index 00000000..e48dffb8 --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/compile/compile.js @@ -0,0 +1,100 @@ +const webpack = require('webpack'); +const MemoryFS = require('memory-fs'); +const clone = require('lodash.clonedeep'); +const path = require('path'); + +const getCompiler = ({ + filename, + mfs, + config +}) => { + const compiler = webpack(config); + + compiler.outputFileSystem = mfs; + + compiler.inputFileSystem.stat = function(path, callback) { + this._statStorage.provide(path, (path, callback) => { + if (path === filename) { + return mfs.stat(path, callback); + } + + this._stat(path, callback); + }, callback); + }; + + compiler.inputFileSystem.readFile = function(path, callback) { + this._readFileStorage.provide(path, (path, callback) => { + if (path === filename) { + return mfs.readFile(path, callback); + } + + this._readFile(path, callback); + }, callback); + }; + + return compiler; +}; + +module.exports = ({ + source, + entrypoint, + config +}, fn) => { + const name = path.basename(entrypoint); + const _filename = path.resolve(config.context, entrypoint); + const _dirname = path.dirname(_filename); + + const mfs = new MemoryFS(); + + mfs.mkdirpSync('/static'); + mfs.mkdirpSync(_dirname); + mfs.writeFileSync(_filename, source); + + const compiler = getCompiler({ + filename: _filename, + mfs, + config: Object.assign(clone(config), { + target: 'node', + output: { + path: '/static', + filename: name + }, + entry: [ + `./${path.relative(config.context, _filename)}` + ] + }) + }); + + compiler.run((err, stats) => { + if (err) { + return fn(err); + } + + const errors = stats.toJson().errors; + + if (errors && errors.length) { + return fn(errors); + } + + mfs.readFile(`/static/${name}`, (err, res) => { + if (err) { + return fn(err); + } + + let style = mfs.readdirSync('/static').filter((file) => { + return /\.css$/.test(file); + }).map((file) => { + try { + return mfs.readFileSync(`/static/${file}`, 'utf-8'); + } catch (err) { + return ''; + } + }).concat('\n'); + + fn(err, { + body: (res && res.toString) ? res.toString() : res, + style + }); + }); + }); +}; diff --git a/ui/webpack/embed-markdown-loader/src/compile/detach.js b/ui/webpack/embed-markdown-loader/src/compile/detach.js new file mode 100644 index 00000000..9a22e39e --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/compile/detach.js @@ -0,0 +1,51 @@ +const spawn = require('child_process').spawn; +const path = require('path'); + +const executable = path.join(__dirname, 'detached.js'); + +module.exports = ({ + source, + entrypoint, + configFullpath +}, fn) => { + let out = ''; + let err = ''; + + const child = spawn('node', [ + executable, + `--entrypoint=${entrypoint}`, + `--config=${configFullpath}` + ]); + + child.stdin.write(source); + child.stdin.end(); + + child.stdout.on('data', (data) => { + out += data; + }); + + child.stderr.on('data', (data) => { + err += data; + }); + + child.on('close', (code) => { + if (code !== 0) { + return fn(new Error(err)); + } + + const res = { + style: '', + body: '' + }; + + try { + const _res = JSON.parse(out); + res.style = _res.style; + res.body = _res.body; + } catch(err) { + console.error(err); + } + + fn(null, res); + }); +}; \ No newline at end of file diff --git a/ui/webpack/embed-markdown-loader/src/compile/detached.js b/ui/webpack/embed-markdown-loader/src/compile/detached.js new file mode 100644 index 00000000..e6cfeac0 --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/compile/detached.js @@ -0,0 +1,25 @@ +const argv = require('minimist')(process.argv.slice(2)); +const getStdin = require('get-stdin'); + +const compile = require('./compile'); + +const { + entrypoint, + config +} = argv; + +const webpackConfig = require(config); + +getStdin().then((source) => { + compile({ + source, + entrypoint, + config: webpackConfig + }, (err, res) => { + if (err) { + throw err; + } + + console.log(JSON.stringify(res)); + }); +}); diff --git a/ui/webpack/embed-markdown-loader/src/compile/index.js b/ui/webpack/embed-markdown-loader/src/compile/index.js new file mode 100644 index 00000000..b4e9de46 --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/compile/index.js @@ -0,0 +1,18 @@ +const compile = require('./compile'); +const detach = require('./detach'); + +module.exports = ({ + source, + entrypoint, + config +}, fn) => { + return !config.fullpath ? compile({ + source, + entrypoint, + config: config.instantiated + }, fn) : detach({ + source, + entrypoint, + configFullpath: config.fullpath + }, fn); +}; diff --git a/ui/webpack/embed-markdown-loader/src/eval.js b/ui/webpack/embed-markdown-loader/src/eval.js new file mode 100644 index 00000000..e45c2560 --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/eval.js @@ -0,0 +1,29 @@ +const Module = require('module'); +const vm = require('vm'); +const path = require('path'); + +module.exports = ({ + source, + entrypoint +}) => { + const script = vm.createScript(source, entrypoint); + const dirname = path.basename(entrypoint); + const rootName = path.join(dirname, '@root'); + + const _module = new Module(rootName); + + _module.filename = rootName; + _module.paths = Module._nodeModulePaths(dirname); + + script.runInNewContext({ + nmodule: _module, + nrequire: _module.require, + __filename: entrypoint, + __dirname: dirname, + process, + console, + Buffer + }); + + return _module.exports; +}; diff --git a/ui/webpack/embed-markdown-loader/src/highlight.js b/ui/webpack/embed-markdown-loader/src/highlight.js new file mode 100644 index 00000000..9dd8fc7a --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/highlight.js @@ -0,0 +1,15 @@ +const hljs = require('highlight.js'); + +module.exports = (str, lang) => { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(lang, str).value; + } catch (err) {} + } + + try { + return hljs.highlightAuto(str).value; + } catch (err) {} + + return ''; +}; diff --git a/ui/webpack/embed-markdown-loader/src/index.js b/ui/webpack/embed-markdown-loader/src/index.js new file mode 100644 index 00000000..695d4807 --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/index.js @@ -0,0 +1,28 @@ +const loaderUtils = require('loader-utils'); +const parse = require('./parse'); +const hl = require('./highlight'); + +module.exports = function(source) { + const fn = this.async(); + + const config = loaderUtils.getLoaderConfig(this, 'embed-markdown-loader'); + const fullname = loaderUtils.getRemainingRequest(this); + const mode = config.mode || 'shadow'; + + parse({ + mode, + fullname, + source, + config: { + webpack: { + instantiated: this._compilation.options, + fullpath: config.webpackConfigFullpath + }, + renderer: { + html: true, + breaks: true, + highlight: hl + } + } + }, fn); +}; diff --git a/ui/webpack/embed-markdown-loader/src/parse.js b/ui/webpack/embed-markdown-loader/src/parse.js new file mode 100644 index 00000000..a3b1982f --- /dev/null +++ b/ui/webpack/embed-markdown-loader/src/parse.js @@ -0,0 +1,98 @@ +const async = require('async'); +const Remarkable = require('remarkable'); +const compile = require('./compile'); +const evaluate = require('./eval'); +const uuid = require('uuid').v4; + +const templates = { + plain: (style, body) => ` + + ${body} + `, + iframe: (style, body) => { + const _body = body.replace(/"/g, '\''); + + return ` + + `; + }, + shadow: (style, body) => { + const id = uuid(); + const script = `(function() { + const element = document.getElementById('${id}').attachShadow({ + mode: 'closed' + }); + + const template = document.getElementById('${id}-template'); + element.appendChild(document.importNode(template.content, true)); + })();`; + + return ` +
+ + + `; + } +}; + + +module.exports = ({ + mode = 'shadow', + fullname, + source, + config = {} +}, fn) => { + const instance = new Remarkable(config.renderer); + + const { + parse, + renderer, + options + } = instance; + + const entrypoint = fullname.replace(/\.md$/, '.js'); + const tokens = parse.call(instance, source, options, {}); + + async.map(tokens, (token, fn) => { + if ((token.type !== 'fence') || (token.params !== 'embed')) { + return fn(null, token); + } + + compile({ + source: token.content, + config: config.webpack, + entrypoint + }, (err, { + body, + style + }) => { + if (err) { + return fn(err); + } + + const evaluated = evaluate({ + entrypoint, + source: body + }); + + const content = templates[mode](style, evaluated); + + return fn(null, { + type: 'htmlblock', + content + }); + }); + }, (err, tokens) => { + if (err) { + return fn(err); + } + + fn(err, renderer.render.call(instance.renderer, tokens, options, {})); + }); +}; diff --git a/ui/webpack/entrypoints.js b/ui/webpack/entrypoints.js new file mode 100644 index 00000000..04228565 --- /dev/null +++ b/ui/webpack/entrypoints.js @@ -0,0 +1,21 @@ +const path = require('path'); +const fs = require('fs'); + +const docs = path.join(__dirname, '../docs/index'); +const src = path.join(__dirname, '../src/'); +const ui = path.join(src, './index.js'); + +module.exports = fs + .readdirSync(src) + .filter((entry) => fs.statSync(path.join(src, entry)).isDirectory()) + .map((entry) => ({ + name: entry, + path: path.join(src, entry, './index.js') + })) + .concat([{ + name: 'docs', + path: docs + }, { + name: 'ui', + path: ui + }]); diff --git a/ui/webpack/index.js b/ui/webpack/index.js new file mode 100644 index 00000000..f1cf1a92 --- /dev/null +++ b/ui/webpack/index.js @@ -0,0 +1,2 @@ +const NODE_ENV = process.env['NODE_ENV'] || 'development'; +module.exports = require(`./${NODE_ENV}`); diff --git a/ui/webpack/production.js b/ui/webpack/production.js new file mode 100644 index 00000000..d6054485 --- /dev/null +++ b/ui/webpack/production.js @@ -0,0 +1,31 @@ +const WebpackShellPlugin = require('webpack-shell-plugin'); +const config = require('./config.js'); +const webpack = require('webpack'); +const entries = require('./entrypoints'); +const path = require('path'); + +module.exports = Object.assign(config, { + entry: entries.reduce((all, entry) => { + all[entry.name] = [entry.path]; + return all; + }, {}), + plugins: config.plugins.concat([ + new webpack.optimize.DedupePlugin(), + new webpack.optimize.OccurrenceOrderPlugin(true), + new webpack.optimize.UglifyJsPlugin(), + new WebpackShellPlugin({ + onBuildEnd: [ + 'npm run build-docs-static' + ] + }) + ]), + devtool: 'eval' +}); + +/* + * Maybe add in the future: + * - https://github.com/lettertwo/appcache-webpack-plugin + * - https://github.com/NekR/offline-plugin + * - https://github.com/goldhand/sw-precache-webpack-plugin + * - https://github.com/Klathmon/imagemin-webpack-plugin + */