diff --git a/.babelrc b/.babelrc index ecd50a14d3..29932a67ba 100644 --- a/.babelrc +++ b/.babelrc @@ -3,8 +3,5 @@ "es2015", "react", "stage-1" - ], - "plugins": [ - "add-module-exports" ] } diff --git a/ENV.js b/ENV.js deleted file mode 100644 index 44baf84870..0000000000 --- a/ENV.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * A utility for determining the current running environment during node processes. - * @readonly - * @type {object} - */ -const ENV = { - setTest() { - process.env.NODE_ENV = 'test' - }, - setStaging() { - process.env.NODE_ENV = 'staging' - }, - setProduction() { - process.env.NODE_ENV = 'production' - }, - setDevelopment() { - process.env.NODE_ENV = 'development' - }, - - isProduction() { - return process.env.NODE_ENV === 'production' - }, - isTest() { - return process.env.NODE_ENV === 'test' - }, - isStaging() { - return process.env.NODE_ENV === 'staging' - }, - isDevelopment() { - return process.env.NODE_ENV === 'development' || !ENV.isProduction() && !ENV.isTest() && !ENV.isStaging() - }, -} - -export default ENV diff --git a/build/karma.conf.babel.js b/build/karma.conf.babel.js new file mode 100644 index 0000000000..874eabce29 --- /dev/null +++ b/build/karma.conf.babel.js @@ -0,0 +1,65 @@ +import { argv } from 'yargs' +import config from '../config' +import webpackConfig from './webpack.config' +const { paths } = config + +module.exports = (karmaConfig) => { + karmaConfig.set({ + basePath: process.cwd(), + browsers: ['PhantomJS'], + singleRun: !argv.watch, + reporters: ['mocha'], + files: [ + './node_modules/babel-polyfill/dist/polyfill.js', + './node_modules/phantomjs-polyfill/bind-polyfill.js', + './test/tests.bundle.js', + ], + frameworks: ['mocha'], + preprocessors: { + '**/*.bundle.js': ['webpack', 'sourcemap'], + }, + client: { + mocha: { + reporter: 'html', // change Karma's debug.html to mocha web reporter + ui: 'bdd', + }, + }, + webpack: { + devtool: 'inline-source-map', + module: { + ...webpackConfig.module, + loaders: [ + { + test: /sinon\.js$/, + loader: 'imports?define=>false,require=>false', + }, + { + test: /\.js$/, + loaders: ['babel', 'eslint'], + exclude: paths.base('node_modules'), + }, + { + test: /\.json$/, + loaders: ['json'], + exclude: paths.base('node_modules'), + }, + ], + }, + resolve: { + ...webpackConfig.resolve, + alias: { + ...webpackConfig.resolve.alias, + jquery: `${paths.test('mocks')}/SemanticjQuery-mock.js`, + sinon: 'sinon/pkg/sinon', + }, + }, + }, + webpackServer: { + progress: false, + stats: config.compiler_stats, + debug: true, + noInfo: false, + quiet: false, + }, + }) +} diff --git a/build/webpack.config.js b/build/webpack.config.js new file mode 100644 index 0000000000..8b23813b73 --- /dev/null +++ b/build/webpack.config.js @@ -0,0 +1,168 @@ +import config from '../config' +import webpack from 'webpack' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import _ from 'lodash' + +const { paths } = config +const { __DEV__, __TEST__ } = config.compiler_globals + +const webpackConfig = { + name: 'client', + target: 'web', + devtool: config.compiler_devtool, + resolve: { + root: paths.base(), + alias: { + stardust: paths.src('index.js'), + }, + }, + externals: { + bluebird: 'Promise', + faker: 'faker', + jquery: 'jQuery', + lodash: '_', + }, + module: {}, +} + +// ------------------------------------ +// Entry Points +// ------------------------------------ + +const webpackHotPath = `${config.compiler_public_path}__webpack_hmr` + +const webpackHotMiddlewareEntry = 'webpack-hot-middleware/client?' + _.map({ + path: webpackHotPath, // The path which the middleware is serving the event stream on + timeout: 2000, // The time to wait after a disconnection before attempting to reconnect + overlay: true, // Set to false to disable the DOM-based client-side overlay. + reload: true, // Set to true to auto-reload the page when webpack gets stuck. + noInfo: false, // Set to true to disable informational console logging. + quiet: false, // Set to true to disable all console logging. +}, (val, key) => `&${key}=${val}`).join('') + +const APP_ENTRY = paths.docsSrc('DocsApp.js') + +webpackConfig.entry = { + app: __DEV__ + ? [webpackHotMiddlewareEntry, APP_ENTRY] + : [APP_ENTRY], + vendor: [ + webpackHotMiddlewareEntry, + 'babel-polyfill', + ...config.compiler_vendor, + ], +} + +// ------------------------------------ +// Bundle Output +// ------------------------------------ +webpackConfig.output = { + filename: `[name].[${config.compiler_hash_type}].js`, + path: config.compiler_output_path, + publicPath: config.compiler_public_path, + pathinfo: true, +} + +// ------------------------------------ +// Plugins +// ------------------------------------ +webpackConfig.plugins = [ + new webpack.DefinePlugin(config.compiler_globals), + new HtmlWebpackPlugin({ + template: paths.docsSrc('index.html'), + hash: false, + filename: 'index.html', + inject: 'body', + minify: { + collapseWhitespace: true, + }, + }), +] + +if (__DEV__) { + webpackConfig.plugins.push( + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin(), + ) +} else if (!__TEST__) { + webpackConfig.plugins.push( + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin({ + compress: { + unused: true, + dead_code: true, + warnings: false, + }, + }) + ) +} + +// Don't split bundles during testing, since we only want import one bundle +if (!__TEST__) { + webpackConfig.plugins.push(new webpack.optimize.CommonsChunkPlugin({ + names: ['vendor'], + })) +} + +// ------------------------------------ +// No Parse +// ------------------------------------ +webpackConfig.module.noParse = [ + /autoit.js/, // highlight.js dep throws if parsed +] + +// ------------------------------------ +// Pre-Loaders +// ------------------------------------ +webpackConfig.module.preLoaders = [{ + test: /\.js$/, + loader: 'eslint', + exclude: /node_modules/, +}] + +webpackConfig.eslint = { + configFile: paths.base('.eslintrc'), + emitWarning: __DEV__, +} + +// ------------------------------------ +// Loaders +// ------------------------------------ +webpackConfig.module.loaders = [{ + // + // Babel + // + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel', + query: { + cacheDirectory: true, + plugins: [], + presets: ['es2015', 'react', 'stage-1'], + env: { + development: { + plugins: [ + ['react-transform', { + transforms: [{ + transform: 'react-transform-hmr', + imports: ['react'], + locals: ['module'], + }, { + transform: 'react-transform-catch-errors', + imports: ['react', 'redbox-react'], + }], + }], + ], + }, + }, + }, +}, { + // + // JSON + // + test: /\.json$/, + loader: 'json', +}] + +export default webpackConfig diff --git a/circle.yml b/circle.yml index ecc3ed2ec5..6ea45d8b3a 100644 --- a/circle.yml +++ b/circle.yml @@ -5,17 +5,13 @@ general: machine: node: - version: 4.2.1 + version: 5 dependencies: pre: - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - npm install -g npm@3 -test: - post: - - nvm install 5 && npm test - deployment: development: branch: /^(?!master).*$/ diff --git a/config/_default.js b/config/_default.js new file mode 100644 index 0000000000..b88a9d9dcb --- /dev/null +++ b/config/_default.js @@ -0,0 +1,117 @@ +import path from 'path' +import { argv } from 'yargs' + +const env = process.env.NODE_ENV || 'development' +const __DEV__ = env === 'development' +const __STAGING__ = env === 'staging' +const __TEST__ = env === 'test' +const __PROD__ = env === 'production' + +let config = { + env, + + // ---------------------------------- + // Project Structure + // ---------------------------------- + path_base: path.resolve(__dirname, '../'), + dir_src: 'src', + dir_dist: 'dist', + dir_docs_root: 'docs', + dir_docs_src: 'docs/app', + dir_docs_dist: 'docs/build', + dir_server: 'server', + dir_test: 'test', +} + +// ------------------------------------ +// Paths +// ------------------------------------ +const base = (...args) => path.resolve(...[config.path_base, ...args]) + +const paths = { + base, + src: base.bind(null, config.dir_src), + dist: base.bind(null, config.dir_dist), + test: base.bind(null, config.dir_test), + docsDist: base.bind(null, config.dir_docs_dist), + docsSrc: base.bind(null, config.dir_docs_src), +} + +config = { + ...config, + paths, + + // ---------------------------------- + // Server Configuration + // ---------------------------------- + server_host: 'localhost', + server_port: process.env.PORT || 8080, + + // ---------------------------------- + // Compiler Configuration + // ---------------------------------- + compiler_devtool: false, + compiler_hash_type: 'hash', + compiler_inline_manifest: false, + compiler_fail_on_warning: false, + compiler_lint: argv.lint !== false, + compiler_quiet: false, + compiler_output_path: paths.base(config.dir_docs_dist), + compiler_public_path: '/', + compiler_vendor: [ + 'bluebird', + 'classnames', + 'faker', + 'jquery', + 'lodash', + 'react', + 'react-dom', + 'react-highlight', + ], + compiler_stats: { + hash: false, // the hash of the compilation + version: false, // webpack version info + timings: true, // timing info + assets: true, // assets info + chunks: false, // chunk info + colors: true, // with console colors + chunkModules: false, // built modules info to chunk info + modules: false, // built modules info + cached: false, // also info about cached (not built) modules + reasons: false, // info about the reasons modules are included + source: false, // the source code of modules + errorDetails: true, // details to errors (like resolving log) + chunkOrigins: false, // the origins of chunks and chunk merging info + modulesSort: '', // (string) sort the modules by that field + chunksSort: '', // (string) sort the chunks by that field + assetsSort: '', // (string) sort the assets by that field + }, + compiler_globals: { + process: { + env: { + NODE_ENV: JSON.stringify(env), + }, + }, + __DEV__, + __DEBUG__: !!argv.debug, + __STAGING__, + __TEST__, + __PROD__, + }, + + // ---------------------------------- + // Test Configuration + // ---------------------------------- + coverage_enabled: !!argv.coverage, + coverage_reporters: [ + { + type: 'text-summary', + }, + { + type: 'lcov', + dir: 'coverage', + }, + ], +} + +export default config diff --git a/config/development.js b/config/development.js new file mode 100644 index 0000000000..50b5e319b4 --- /dev/null +++ b/config/development.js @@ -0,0 +1,7 @@ +export default (config) => ({ + compiler_devtool: 'source-map', + + // We use an explicit public path in development to resolve this issue: + // http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809 + compiler_public_path: `http://${config.server_host}:${config.server_port}/`, +}) diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000000..b466c1a989 --- /dev/null +++ b/config/index.js @@ -0,0 +1,5 @@ +import base from './_default' + +const envConfig = require(`./${base.env}`).default(base) + +export default { ...base, ...envConfig } diff --git a/config/production.js b/config/production.js new file mode 100644 index 0000000000..dfffb1262b --- /dev/null +++ b/config/production.js @@ -0,0 +1,5 @@ +export default () => ({ + compiler_fail_on_warning: true, + compiler_hash_type: 'chunkhash', + compiler_devtool: false, +}) diff --git a/config/staging.js b/config/staging.js new file mode 100644 index 0000000000..750cfe284e --- /dev/null +++ b/config/staging.js @@ -0,0 +1,6 @@ +import config from './production' + +// Enable source-maps in staging +config.compiler_devtool = 'source-map' + +export default config diff --git a/config/test.js b/config/test.js new file mode 100644 index 0000000000..9e80fa6584 --- /dev/null +++ b/config/test.js @@ -0,0 +1,4 @@ +// Just use the production configuration +import production from './production' + +export default production diff --git a/docs/app/Components/ComponentDoc/ComponentExample.js b/docs/app/Components/ComponentDoc/ComponentExample.js index b9e9a56505..2f0ce00bf9 100644 --- a/docs/app/Components/ComponentDoc/ComponentExample.js +++ b/docs/app/Components/ComponentDoc/ComponentExample.js @@ -19,7 +19,7 @@ export default class ComponentExample extends Component { super(props, context) this.state = { showCode: false } this.fileContents = require(`!raw!docs/app/Examples/${props.examplePath}`) - this.component = exampleContext(`./${props.examplePath}.js`) + this.component = exampleContext(`./${props.examplePath}.js`).default // 'elements/Button/Types/Button' => #Button-Types-Button this.anchor = props.examplePath.split('/').slice(1).join('-') } diff --git a/docs/app/Components/ComponentDoc/ComponentExamples.js b/docs/app/Components/ComponentDoc/ComponentExamples.js index 677632736e..0b581e4711 100644 --- a/docs/app/Components/ComponentDoc/ComponentExamples.js +++ b/docs/app/Components/ComponentDoc/ComponentExamples.js @@ -11,7 +11,7 @@ export default class ComponentExamples extends Component { const examples = exampleContext.keys() .filter(path => path.includes(`/${this.props.name}Examples.js`)) .map((path, i) => { - const Example = exampleContext(path) + const Example = exampleContext(path).default return }) diff --git a/docs/app/Components/Sidebar/Sidebar.js b/docs/app/Components/Sidebar/Sidebar.js index a9b5db5799..9437b2881c 100644 --- a/docs/app/Components/Sidebar/Sidebar.js +++ b/docs/app/Components/Sidebar/Sidebar.js @@ -1,8 +1,10 @@ import _ from 'lodash' import React, { Component } from 'react' -import stardust, { Menu, Input } from 'stardust' +import * as stardust from 'stardust' import META from 'src/utils/Meta' +const { Menu, Input } = stardust + export default class Sidebar extends Component { state = { query: '' }; diff --git a/docs/app/DocsApp.js b/docs/app/DocsApp.js index 31257bf3c1..15ffd586f4 100644 --- a/docs/app/DocsApp.js +++ b/docs/app/DocsApp.js @@ -1,11 +1,13 @@ import React, { Component } from 'react' import { render } from 'react-dom' -import stardust, { Grid } from 'stardust' +import * as stardust from 'stardust' -import ComponentDoc from 'docs/app/Components/ComponentDoc/ComponentDoc' -import DocsMenu from 'Components/Sidebar/Sidebar' +import ComponentDoc from './Components/ComponentDoc/ComponentDoc' +import DocsMenu from './Components/Sidebar/Sidebar' import style from './Style' +const { Grid } = stardust + class DocsApp extends Component { state = { menuSearch: '' }; diff --git a/docs/app/Examples/elements/Icon/IconSet/IconWebContentExample.js b/docs/app/Examples/elements/Icon/IconSet/IconWebContentExample.js index 500a35dca4..cb2962f4ad 100644 --- a/docs/app/Examples/elements/Icon/IconSet/IconWebContentExample.js +++ b/docs/app/Examples/elements/Icon/IconSet/IconWebContentExample.js @@ -12,7 +12,7 @@ const iconClasses = [ ] const icons = _.map(iconClasses, iconClass => ( - +
{_.startCase(iconClass)}
diff --git a/docs/app/index.html b/docs/app/index.html index f00682f7c8..6875f598b2 100644 --- a/docs/app/index.html +++ b/docs/app/index.html @@ -1,7 +1,7 @@ - Stardust Docs + Stardust @@ -15,7 +15,5 @@
- - diff --git a/docs/app/utils/getComponentDocInfo.js b/docs/app/utils/getComponentDocInfo.js index acdd8949c5..d5bd3dc3f5 100644 --- a/docs/app/utils/getComponentDocInfo.js +++ b/docs/app/utils/getComponentDocInfo.js @@ -6,7 +6,7 @@ import docgenInfo from '../docgenInfo.json' * @param {string} component Stardust component name. * @returns {{}} Documentation object. */ -export default component => { +export default (component) => { const doc = {} doc.name = component doc.path = _.filter(_.keys(docgenInfo), path => _.includes(path, `/${component}.js`))[0] diff --git a/gulp/gulphelp.js b/gulp/gulphelp.js deleted file mode 100644 index 3d705a3f30..0000000000 --- a/gulp/gulphelp.js +++ /dev/null @@ -1,9 +0,0 @@ -// https://github.com/chmontgomery/gulp-help#requiregulp-helprequiregulp-options - -export default { - description: '', // modifies the default help message - aliases: [], // adds aliases to the default help task - hideEmpty: true, // hide all tasks with no help message - hideDepsMessage: true, // hide all task dependencies - afterPrintCallback: () => {}, // run after the default help task runs -} diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js deleted file mode 100644 index d741c002fa..0000000000 --- a/gulp/tasks/default.js +++ /dev/null @@ -1,16 +0,0 @@ -import defaultGulp from 'gulp' -import helpConfig from '../gulphelp' -import loadPlugins from 'gulp-load-plugins' -import runSequence from 'run-sequence' - -const g = loadPlugins() -const gulp = g.help(defaultGulp, helpConfig) - -gulp.task('default', cb => { - runSequence( - 'docs', - 'serve', - 'watch', - cb - ) -}) diff --git a/gulp/tasks/docs.js b/gulp/tasks/docs.js index b13f6aac1d..820e91d5b5 100644 --- a/gulp/tasks/docs.js +++ b/gulp/tasks/docs.js @@ -1,75 +1,58 @@ import del from 'del' -import defaultGulp from 'gulp' -import helpConfig from '../gulphelp' +import { task, src, dest, series } from 'gulp' import loadPlugins from 'gulp-load-plugins' -import runSequence from 'run-sequence' - -const g = loadPlugins() -const gulp = g.help(defaultGulp, helpConfig) - -import ENV from '../../ENV' -import config from '../../webpack.dev.babel' -import paths from '../../paths' -import statsConfig from '../../webpack-stats' import webpack from 'webpack' -gulp.task('docs', 'build doc sites', cb => { - runSequence( - 'clean-docs', - 'build-docs-html', - 'generate-docs-json', - 'webpack-docs', - cb - ) -}) +import config from '../../config' +import webpackConfig from '../../build/webpack.config' -gulp.task('clean-docs', cb => { - del.sync(paths.docsBuild) - cb() -}) +const g = loadPlugins() +const { log, PluginError } = g.util -gulp.task('generate-docs-json', cb => { +task('clean-docs', () => del(config.paths.docsDist())) + +task('generate-docs-json', () => { const gulpReactDocgen = require('../plugins/gulp-react-docgen') - return gulp.src([ - paths.srcAddons + '/**/*.js', - paths.srcElements + '/**/*.js', - paths.srcCollections + '/**/*.js', - paths.srcModules + '/**/*.js', - paths.srcViews + '/**/*.js', - '!' + paths.src + '/**/Style.js', + return src([ + config.paths.src() + '/addons/**/*.js', + config.paths.src() + '/elements/**/*.js', + config.paths.src() + '/collections/**/*.js', + config.paths.src() + '/modules/**/*.js', + config.paths.src() + '/views/**/*.js', ]) // do not remove the function keyword // we need 'this' scope here .pipe(g.plumber(function handleError(err) { - g.util.log(err) + log(err) this.emit('end') })) .pipe(gulpReactDocgen()) - .pipe(gulp.dest(paths.docsApp)) + .pipe(dest(config.paths.docsSrc())) }) -gulp.task('webpack-docs', cb => { - webpack(config, (err, stats) => { - if (err) throw new g.util.PluginError('webpack', err) +task('webpack-docs', (cb) => { + const compiler = webpack(webpackConfig) + + compiler.run((err, stats) => { + const { errors, warnings } = stats.toJson() + + log(stats.toString(config.compiler_stats)) - g.util.log( - g.util.colors.cyan('Docs bundle:'), - stats.toString(statsConfig) - ) + if (err) { + log('Webpack compiler encountered a fatal error.') + throw new PluginError('webpack', err.toString()) + } + if (errors.length > 0) { + log('Webpack compiler encountered errors.') + throw new PluginError('webpack', errors.toString()) + } + if (warnings.length > 0 && config.compiler_fail_on_warning) { + throw new PluginError('webpack', warnings.toString()) + } cb(err) }) }) -gulp.task('build-docs-html', cb => { - const replaceOpts = { - keepUnassigned: true, // keep build blocks without a defined replacement - } - const replaceTasks = {} - replaceTasks[ENV.isProduction() ? 'development' : 'production'] = '' - - return gulp.src(paths.docsApp + '/**/*.html') - .pipe(g.htmlReplace(replaceTasks, replaceOpts)) - .pipe(gulp.dest(paths.docsBuild)) -}) +task('docs', series('clean-docs', 'generate-docs-json', 'webpack-docs')) diff --git a/gulp/tasks/serve.js b/gulp/tasks/serve.js index 513e2b4a7b..b55e33cb2b 100644 --- a/gulp/tasks/serve.js +++ b/gulp/tasks/serve.js @@ -1,53 +1,42 @@ -import childProcess from 'child_process' -import defaultGulp from 'gulp' -import helpConfig from '../gulphelp' +import { series, task } from 'gulp' import loadPlugins from 'gulp-load-plugins' +import express from 'express' import webpack from 'webpack' -import WebpackDevServer from 'webpack-dev-server' +import WebpackDevMiddleware from 'webpack-dev-middleware' +import WebpackHotMiddleware from 'webpack-hot-middleware' +import historyApiFallback from 'connect-history-api-fallback' -const g = loadPlugins() -const gulp = g.help(defaultGulp, helpConfig) - -import paths from '../../paths' -import statsConfig from '../../webpack-stats' -import webpackConfig from '../../webpack.dev.babel' - -gulp.task('serve', 'serve, build (in memory only), and watch the app', cb => { - const host = 'localhost' - const port = '8080' - const protocol = 'http' - const serverUrl = `${protocol}://${host}:${port}` - - // http://webpack.github.io/docs/webpack-dev-server.html#api - const devMiddlewareConfig = { - contentBase: paths.docsBuild, - historyApiFallback: true, - hot: true, - quiet: false, // log nothing - noInfo: true, // log only warnings and errors - - // http://webpack.github.io/docs/node.js-api.html#stats - stats: statsConfig, - } - - // http://webpack.github.io/docs/configuration.html - const compiler = webpack(webpackConfig) - - function onComplete(err, stdout, stderr) { - cb(err) - } +import config from '../../config' +import webpackConfig from '../../build/webpack.config' - new WebpackDevServer(compiler, devMiddlewareConfig) - .listen(port, host, err => { - if (err) { - throw new g.util.PluginError('webpack-dev-server', err) - } +const g = loadPlugins() +const { log, colors } = g.util - g.util.log( - g.util.colors.green('[webpack-dev-server]'), - serverUrl - ) +const serve = (cb) => { + const app = express() + const compiler = webpack(webpackConfig) - childProcess.exec('open ' + serverUrl, onComplete) + app + .use(historyApiFallback({ + verbose: false, + })) + + .use(WebpackDevMiddleware(compiler, { + publicPath: webpackConfig.output.publicPath, + contentBase: config.paths.docsSrc(), + hot: true, + quiet: false, + noInfo: true, // must be quiet for hot middleware to show overlay + lazy: false, + stats: config.compiler_stats, + })) + + .use(WebpackHotMiddleware(compiler)) + + .listen(config.server_port, config.server_host, () => { + log(colors.yellow('Server running at http://%s:%d'), config.server_host, config.server_port) + cb() }) -}) +} + +task('serve', series('clean-docs', 'generate-docs-json', serve)) diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js index 9cb57d5a78..f465b1b10e 100644 --- a/gulp/tasks/watch.js +++ b/gulp/tasks/watch.js @@ -1,16 +1,15 @@ -import defaultGulp from 'gulp' -import helpConfig from '../gulphelp' +import { task, watch, series } from 'gulp' import loadPlugins from 'gulp-load-plugins' +import config from '../../config' const g = loadPlugins() -const gulp = g.help(defaultGulp, helpConfig) +const { log } = g.util -import paths from '../../paths' +task('watch', (cb) => { + const handleChange = (e) => log(`File ${e.path} was ${e.type}, running tasks...`) -gulp.task('watch', 'watch and build docs', cb => { - gulp.watch([paths.src + '/**/*.js'], [ - 'generate-docs-json', // rebuild doc info - ]) - gulp.watch([paths.docsRoot + '/**/*.html'], ['build-docs-html']) + // rebuild doc info + watch(config.paths.src() + '/**/*.js', series('generate-docs-json')) + .on('change', handleChange) cb() }) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 1246d475da..fe7cd6e58d 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -2,7 +2,12 @@ * Tasks live in the tasks directory. * This file just loads all the gulp tasks. */ - +import gulp from 'gulp' import requireDir from 'require-dir' -export default requireDir('./gulp/tasks') +requireDir('./gulp/tasks') + +// do not use tasks/default +// the default task must be loaded after all other tasks +// requireDir above loads all our tasks in alphabetical order +gulp.task('default', gulp.series('serve')) diff --git a/index.html b/index.html deleted file mode 100644 index 863544962f..0000000000 --- a/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/karma.conf.babel.js b/karma.conf.babel.js deleted file mode 100644 index 1e58d6dd9e..0000000000 --- a/karma.conf.babel.js +++ /dev/null @@ -1,72 +0,0 @@ -import paths from './paths' -import statConfig from './webpack-stats' -import exitPlugin from './webpack-exit-plugin' -import ENV from './ENV' - -/** - * This config is for running tests on the command line and will fail on errors. - * @param {{}} config Karma config object. - * @type {{}} - */ -export default (config) => { - config.set({ - browsers: ['PhantomJS'], - singleRun: !ENV.isDevelopment(), - reporters: [ENV.isDevelopment() ? 'mocha' : 'dots'], - files: [ - './node_modules/phantomjs-polyfill/bind-polyfill.js', - './test/tests.bundle.js', - ], - frameworks: [ - 'mocha', - ], - preprocessors: { - '**/*.bundle.js': ['webpack', 'sourcemap'], - }, - client: { - mocha: { - // require: '', - reporter: 'html', // change Karma's debug.html to mocha web reporter - ui: 'bdd', - }, - }, - webpack: { - // karma watches the test entry points - // (you don't need to specify the entry option) - // webpack watches dependencies - devtool: 'inline-source-map', - module: { - loaders: [ - { - test: /\.js$/, - loaders: ['babel', 'eslint'], - exclude: paths.node_modules, - }, - { - test: /\.json$/, - loaders: ['json'], - exclude: paths.node_modules, - }, - ], - }, - resolve: { - root: paths.root, - alias: { - // When these key names are require()'d, the value will be supplied instead - jquery: paths.testMocks + '/SemanticjQuery-mock.js', - stardust: paths.src + '/index.js', - }, - }, - plugins: !ENV.isDevelopment() - ? [exitPlugin] - : [], - }, - webpackServer: { - progress: false, - stats: statConfig, - debug: true, - noInfo: false, - quiet: false, - }, - }) -} diff --git a/package.json b/package.json index 8c1ad4c5c1..7287e02923 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "dist" ], "scripts": { - "build": "babel src -d dist", + "build": "rimraf dist && babel src -d dist", + "docs": "gulp docs", "clean": "while read line; do rm -rf $line; done < .gitignore", - "deploy:docs": "gulp docs && $(npm bin)/gh-pages -d docs/build -m 'deploy docs [ci skip]'", + "predeploy:docs": "npm run docs", + "deploy:docs": "gh-pages -d docs/build -m 'deploy docs [ci skip]'", "lint": "eslint .", "lint:fix": "npm run lint -- --fix", "lint:watch": "watch 'npm run lint' docs gulp src test", @@ -19,9 +21,9 @@ "release:minor": "ta-script npm/release.sh minor", "release:patch": "ta-script npm/release.sh patch", "start": "gulp", - "karma": "babel-node $(npm bin)/karma start", - "test": "npm run lint && npm run karma -- karma.conf.babel.js", - "test:watch": "webpack-dev-server --config webpack.tests.babel.js" + "pretest": "npm run lint --silent", + "test": "NODE_ENV=test babel-node $(npm bin)/karma start build/karma.conf.babel.js", + "test:watch": "npm run test --silent -- --watch" }, "repository": { "type": "git", @@ -47,11 +49,12 @@ "babel-core": "^6.5.2", "babel-eslint": "^4.1.6", "babel-loader": "^6.2.0", - "babel-plugin-add-module-exports": "^0.1.2", + "babel-plugin-react-transform": "^2.0.2", "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-1": "^6.5.0", "chai": "^3.3.0", + "connect-history-api-fallback": "^1.2.0", "del": "^2.0.2", "dirty-chai": "^1.2.2", "doctrine": "^0.7.0", @@ -60,15 +63,16 @@ "eslint-loader": "^1.0.0", "eslint-plugin-mocha": "^1.0.0", "eslint-plugin-react": "^3.5.1", + "express": "^4.13.4", "faker": "^3.0.1", "gh-pages": "^0.11.0", - "gulp": "^3.9.0", - "gulp-help": "^1.6.1", + "gulp": "github:gulpjs/gulp#4.0", "gulp-html-replace": "^1.5.4", "gulp-load-plugins": "^0.10.0", "gulp-plumber": "^1.0.1", "gulp-util": "^3.0.6", "highlight.js": "8.9.1", + "html-webpack-plugin": "^2.14.0", "imports-loader": "^0.6.4", "json": "^9.0.3", "json-loader": "^0.5.3", @@ -88,17 +92,22 @@ "react-addons-test-utils": "^0.14.0", "react-docgen": "^2.2.0", "react-dom": "^0.14.0", - "react-highlight": "0.5.1", + "react-highlight": "^0.7.0", "react-hot-loader": "^1.3.0", + "react-transform-catch-errors": "^1.0.2", + "react-transform-hmr": "^1.0.4", + "redbox-react": "^1.2.2", "require-dir": "^0.3.0", - "run-sequence": "^1.1.4", - "sinon": "git://github.com/cjohansen/Sinon.JS#b672042043517b9f84e14ed0fb8265126168778a", + "rimraf": "^2.5.2", + "sinon": "^1.17.3", "sinon-chai": "^2.8.0", "ta-scripts": "^2.3.1", "through2": "^2.0.0", "watch": "^0.16.0", - "webpack": "1.12.2", - "webpack-dev-server": "1.10.1" + "webpack": "^1.12.2", + "webpack-dev-middleware": "^1.5.1", + "webpack-hot-middleware": "^2.10.0", + "yargs": "^4.3.2" }, "peerDependencies": { "jquery": "^2.1.4", diff --git a/paths.js b/paths.js deleted file mode 100644 index f71cc8fe66..0000000000 --- a/paths.js +++ /dev/null @@ -1,22 +0,0 @@ -import path from 'path' -const PROJECT_ROOT = path.resolve(__dirname) - -const PATHS = { - root: PROJECT_ROOT + '/', - docsRoot: PROJECT_ROOT + '/docs', - docsApp: PROJECT_ROOT + '/docs/app', - docsBuild: PROJECT_ROOT + '/docs/build', - gulp: PROJECT_ROOT + '/gulp', - src: PROJECT_ROOT + '/src', - srcAddons: PROJECT_ROOT + '/src/addons', - srcElements: PROJECT_ROOT + '/src/elements', - srcCollections: PROJECT_ROOT + '/src/collections', - srcModules: PROJECT_ROOT + '/src/modules', - srcViews: PROJECT_ROOT + '/src/views', - srcUtils: PROJECT_ROOT + '/src/utils', - test: PROJECT_ROOT + '/test', - testMocks: PROJECT_ROOT + '/test/mocks', - node_modules: PROJECT_ROOT + '/node_modules', -} - -export default PATHS diff --git a/src/index.js b/src/index.js index 754bec34bb..8a608322cb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,98 +1,71 @@ -import { deprecateComponents } from './utils/deprecate' +import { deprecateComponent } from './utils/deprecate' +// ---------------------------------------- // Addons -import Confirm from './addons/Confirm/Confirm' -import Textarea from './addons/Textarea/Textarea' +// ---------------------------------------- +export Confirm from './addons/Confirm/Confirm' +export Textarea from './addons/Textarea/Textarea' +// ---------------------------------------- // Collections -import Form from './collections/Form/Form' -import FormField from './collections/Form/FormField' -import FormFields from './collections/Form/FormFields' -import Grid from './collections/Grid/Grid' -import GridColumn from './collections/Grid/GridColumn' -import GridRow from './collections/Grid/GridRow' -import Menu from './collections/Menu/Menu' -import MenuItem from './collections/Menu/MenuItem' -import Message from './collections/Message/Message' -import Table from './collections/Table/Table' -import TableColumn from './collections/Table/TableColumn' +// ---------------------------------------- -// Elements -import Button from './elements/Button/Button' -import Buttons from './elements/Button/Buttons' -import Container from './elements/Container/Container' -import Divider from './elements/Divider/Divider' -import Header from './elements/Header/Header' -import Icon from './elements/Icon/Icon' -import Image from './elements/Image/Image' -import Input from './elements/Input/Input' -import List from './elements/List/List' -import ListItem from './elements/List/ListItem' -import Segment from './elements/Segment/Segment' -import Segments from './elements/Segment/Segments' +import _Form from './collections/Form/Form' +export { _Form as Form } +export const Field = deprecateComponent('Field', 'Use "Form.Field" instead.', _Form.Field) +export const Fields = deprecateComponent('Fields', 'Use "Form.Fields" instead.', _Form.Fields) -// Modules -import Checkbox from './modules/Checkbox/Checkbox' -import Progress from './modules/Progress/Progress' -import Modal from './modules/Modal/Modal' -import ModalContent from './modules/Modal/ModalContent' -import ModalFooter from './modules/Modal/ModalFooter' -import ModalHeader from './modules/Modal/ModalHeader' -import Dropdown from './modules/Dropdown/Dropdown' +import _Grid from './collections/Grid/Grid' +export { _Grid as Grid } +export const Column = deprecateComponent('Column', 'Use "Grid.Column" instead.', _Grid.Column) +export const Row = deprecateComponent('Row', 'Use "Grid.Row" instead.', _Grid.Row) -// Views -import Item from './views/Items/Item' -import Items from './views/Items/Items' -import Statistic from './views/Statistic/Statistic' +import _Menu from './collections/Menu/Menu' +export { _Menu as Menu } +export const MenuItem = deprecateComponent('MenuItem', 'Use "Menu.Item" instead.', _Menu.Item) +export Message from './collections/Message/Message' + +import _Table from './collections/Table/Table' +export { _Table as Table } +export const TableColumn = deprecateComponent('TableColumn', 'Use "Table.Column" instead.', _Table.Column) -const stardust = { - // Addons - Confirm, - Textarea, +// ---------------------------------------- +// Elements +// ---------------------------------------- +export Button from './elements/Button/Button' +export Buttons from './elements/Button/Buttons' +export Container from './elements/Container/Container' +export Divider from './elements/Divider/Divider' +export Header from './elements/Header/Header' +export Icon from './elements/Icon/Icon' +export Image from './elements/Image/Image' +export Input from './elements/Input/Input' + +import _List from './elements/List/List' +export { _List as List } +export const ListItem = deprecateComponent('ListItem', 'Use "List.Item" instead.', _List.Item) - // Collections - Form, - Grid, - Menu, - Message, - Table, +export Segment from './elements/Segment/Segment' +export Segments from './elements/Segment/Segments' - // Elements - Button, - Buttons, - Container, - Divider, - Header, - Icon, - Image, - Input, - List, - Segment, - Segments, +// ---------------------------------------- +// Modules +// ---------------------------------------- +export Checkbox from './modules/Checkbox/Checkbox' +export Progress from './modules/Progress/Progress' - // Modules - Checkbox, - Dropdown, - Modal, - Progress, +import _Modal from './modules/Modal/Modal' +export { _Modal as Modal } +export const ModalContent = deprecateComponent('ModalContent', 'Use "Modal.Content" instead.', _Modal.Content) +export const ModalFooter = deprecateComponent('ModalFooter', 'Use "Modal.Footer" instead.', _Modal.Footer) +export const ModalHeader = deprecateComponent('ModalHeader', 'Use "Modal.Header" instead.', _Modal.Header) - // Views - Item, - Items, - Statistic, -} +export Dropdown from './modules/Dropdown/Dropdown' -deprecateComponents(stardust, [ - ['Field', FormField, 'Use "Form.Field" instead.'], - ['Fields', FormFields, 'Use "Form.Fields" instead.'], - ['Column', GridColumn, `Use "Grid.Column" instead.`], - ['TableColumn', TableColumn, `Use "Table.Column" instead.`], - ['ListItem', ListItem, `Use "List.Item" instead.`], - ['MenuItem', MenuItem, `Use "Menu.Item" instead.`], - ['ModalContent', ModalContent, `Use "Modal.Content" instead.`], - ['ModalFooter', ModalFooter, `Use "Modal.Footer" instead.`], - ['ModalHeader', ModalHeader, `Use "Modal.Header" instead.`], - ['Row', GridRow, `Use "Grid.Row" instead.`], -]) +// ---------------------------------------- +// Views +// ---------------------------------------- -export default stardust +export Item from './views/Items/Item' +export Items from './views/Items/Items' +export Statistic from './views/Statistic/Statistic' diff --git a/src/utils/deprecate.js b/src/utils/deprecate.js index 00af3a4f2c..2c84de8edc 100644 --- a/src/utils/deprecate.js +++ b/src/utils/deprecate.js @@ -18,19 +18,19 @@ export const deprecateProps = (context, deprecated) => { /** * Show a deprecated warning for Stardust components. - * @param {object} stardust A reference to Stardust. - * @param {array} deprecated Two dimensional array. - * Elements in second dimension are deprecated components/warning messages. - * The component name and "is deprecated" is automatically added. - * Only add help messages as values. + * @param {String} name Name of the component being deprecated. + * @param {String} warning Message to display to the user. + * @param {String} Replacement Component to be returned in its place. + * @returns {DeprecatedComponent} */ -export const deprecateComponents = (stardust, deprecated) => { - _.each(deprecated, ([name, component, warning]) => { - Object.defineProperty(stardust, name, { - get() { - console.warn(`Stardust component "${name}" is deprecated. ${warning}`) - return component - }, - }) - }) +export const deprecateComponent = (name, warning, Replacement) => { + return class DeprecatedComponent extends Replacement { + constructor(...args) { + super(...args) + console.warn(warning + ? `Stardust component "${name}" is deprecated. ${warning}` + : `"${name}" will be removed in future versions. ${warning}` + ) + } + } } diff --git a/test/specs/index-test.js b/test/specs/index-test.js index a53945c7c7..9660fcf1d4 100644 --- a/test/specs/index-test.js +++ b/test/specs/index-test.js @@ -1,5 +1,5 @@ import _ from 'lodash' -import stardust from 'stardust' +import * as stardust from 'stardust' import META from '../../src/utils/Meta' import componentInfo from '../utils/componentInfo' @@ -8,10 +8,10 @@ describe('stardust (index.js)', () => { const { _meta, constructorName, subComponentName } = info const isPrivate = META.isPrivate(constructorName) - // stardust.H1 + // handle components: stardust.H1 const isStardustProp = _.has(stardust, constructorName) - // stardust.Form.Field (ie FormField component) + // handle sub components: stardust.Form.Field (ie FormField component) // // only search the 'parent' for the sub component // avoids false positives like DropdownItem & MenuItem diff --git a/test/tests.bundle.js b/test/tests.bundle.js index c0a118aeb7..cb9ef4d44a 100644 --- a/test/tests.bundle.js +++ b/test/tests.bundle.js @@ -1,12 +1,3 @@ -import _ from 'lodash' - -// clear the console before rebundling. -/* eslint-disable no-console */ -if (_.isFunction(console.clear)) { - console.clear() -} -/* eslint-enable no-console */ - // setup const setupContext = require.context('./', true, /setup\.js$/) setupContext.keys().forEach(setupContext) diff --git a/test/utils/componentInfo.js b/test/utils/componentInfo.js index a36ae841b6..ada63a3b8a 100644 --- a/test/utils/componentInfo.js +++ b/test/utils/componentInfo.js @@ -8,7 +8,7 @@ const componentCtx = require.context( ) const componentInfo = _.map(componentCtx.keys(), key => { - const Component = componentCtx(key) + const Component = componentCtx(key).default const { _meta, prototype } = Component const constructorName = prototype.constructor.name diff --git a/webpack-exit-plugin.js b/webpack-exit-plugin.js deleted file mode 100644 index 0005ec53f9..0000000000 --- a/webpack-exit-plugin.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Simple plugin to cause Webpack to exit with code 1 when there are compilation errors. - * Without this, when lint/tests fail, Webpack will exit code 0 (success) and CI is hosed. - */ -function failOnError() { - this.plugin('done', (stats) => { - if (stats.compilation.errors && stats.compilation.errors.length) { - stats.compilation.errors.forEach((err) => { - /* eslint-disable no-console */ - console.log(err.toString()) - /* eslint-enable no-console */ - }) - process.exit(1) - } - }) -} - -module.exports = failOnError diff --git a/webpack-stats.js b/webpack-stats.js deleted file mode 100644 index fed50b0c31..0000000000 --- a/webpack-stats.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Webpack dev server command line output config. Used after a bundle is created. - * @type {{}} - */ -module.exports = { - hash: false, // the hash of the compilation - version: false, // webpack version info - timings: true, // timing info - assets: true, // assets info - chunks: false, // chunk info - colors: true, // with console colors - chunkModules: false, // built modules info to chunk info - modules: false, // built modules info - cached: false, // also info about cached (not built) modules - reasons: false, // info about the reasons modules are included - source: false, // the source code of modules - errorDetails: true, // details to errors (like resolving log) - chunkOrigins: false, // the origins of chunks and chunk merging info - modulesSort: '', // (string) sort the modules by that field - chunksSort: '', // (string) sort the chunks by that field - assetsSort: '', // (string) sort the assets by that field -} diff --git a/webpack.dev.babel.js b/webpack.dev.babel.js deleted file mode 100644 index 9f3478943f..0000000000 --- a/webpack.dev.babel.js +++ /dev/null @@ -1,67 +0,0 @@ -import paths from './paths' -import webpack from 'webpack' - -/** - * This config builds and serves the doc site with webpack dev server. - * @type {{}} - */ -export default { - entry: { - app: [ - 'webpack-dev-server/client?http://localhost:8080', - 'webpack/hot/dev-server', - paths.docsApp + '/DocsApp.js', - ], - vendor: [ - 'bluebird', - 'classnames', - 'faker', - 'jquery', - 'lodash', - 'react', - 'react-highlight', - ], - }, - output: { - path: paths.docsBuild, - filename: '[name].js', - }, - module: { - noParse: [/autoit.js/], - loaders: [ - { - test: /\.js$/, - loaders: ['react-hot', 'babel', 'eslint'], - exclude: paths.node_modules, - }, - { - test: /\.json$/, - loaders: ['json'], - exclude: paths.node_modules, - }, - ], - }, - externals: { - bluebird: 'Promise', - faker: 'faker', - jquery: 'jQuery', - lodash: '_', - }, - devtool: 'source-map', - resolve: { - root: [ - paths.docsRoot, - ], - modulesDirectories: [ - 'node_modules', - '.', - ], - alias: { - stardust: `${paths.src}/index.js`, - }, - }, - plugins: [ - new webpack.HotModuleReplacementPlugin(), - new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), - ], -} diff --git a/webpack.tests.babel.js b/webpack.tests.babel.js deleted file mode 100644 index b76a353f15..0000000000 --- a/webpack.tests.babel.js +++ /dev/null @@ -1,65 +0,0 @@ -import paths from './paths' -import statConfig from './webpack-stats' - -/** - * This config is for writing tests. Results are shown in browser with livereload. - * It also includes linting results in browser. - * @type {{}} - */ -module.exports = { - entry: './test/tests.bundle.js', - output: { - path: './', - filename: 'testBundle.js', - }, - devtool: 'inline-source-map', - module: { - loaders: [ - { - // Sinon has a screwed up AMD module definition that breaks webpack - // This removes the `define` method to monkey patch it for webpack - // https://github.com/webpack/webpack/issues/177 - // - // Note we also have to use this sinon: - // git://github.com/cjohansen/Sinon.JS#b672042043517b9f84e14ed0fb8265126168778a - test: /sinon.*\.js$/, - loader: 'imports?define=>false', - include: [paths.node_modules], - }, - { - test: /\.js$/, - loaders: ['babel', 'eslint'], - exclude: paths.node_modules, - }, - { - test: /\.json$/, - loaders: ['json'], - exclude: paths.node_modules, - }, - ], - postLoaders: [ - { - test: /(setup|-test)\.js$/, - loader: 'mocha', - include: [paths.test], - }, - ], - }, - resolve: { - root: paths.root, - alias: { - // When these key names are require()'d, the value will be supplied instead - jquery: paths.testMocks + '/SemanticjQuery-mock.js', - stardust: `${paths.src}/index.js`, - }, - }, - plugins: [], - devServer: { - progress: false, - stats: statConfig, - debug: true, - noInfo: false, - quiet: false, - }, - debug: true, -}