From e73f2fe0a0cd4e5e26d614b38181b99921fe47c7 Mon Sep 17 00:00:00 2001 From: Les Orchard Date: Fri, 15 Sep 2017 12:22:50 -0400 Subject: [PATCH] Tweaks to support component directory bundles - Storybook now also finds stories in frontend/src/app/**/stories.js - `npm run test` now also finds tests in frontend/src/app/**/tests.js - New /static/app/app.js.css stylesheet built from Sass styles imported by components - Hacks to ignore .scss imports when using component modules outside Webpack for static page build & tests - Update flowconfig to ignore .scss imports - Small eslint upgrade so CLI matches gulp-eslint Issue #2807 --- .eslintrc | 8 +++++++- .storybook/config.js | 2 ++ .storybook/webpack.config.js | 23 +++++++++++++++++++++++ frontend/.flowconfig | 5 +++++ frontend/tasks/pages.js | 1 + frontend/test-setup.js | 3 +++ gulpfile.babel.js | 3 +++ package.json | 12 ++++++++---- webpack.config.js | 23 ++++++++++++++++++++--- 9 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 .storybook/webpack.config.js diff --git a/.eslintrc b/.eslintrc index 3ce3c13682..32bb267f11 100644 --- a/.eslintrc +++ b/.eslintrc @@ -34,6 +34,8 @@ rules: comma-dangle: [2, never] indent: [2, 2, {SwitchCase: 1}] max-len: [0] + one-var: off + one-var-declaration-per-line: off no-console: 1 no-multi-spaces: [0] no-param-reassign: [0] @@ -44,4 +46,8 @@ rules: linebreak-style: [0] "import/no-extraneous-dependencies": - error - - { devDependencies: [ "./frontend/{stories,tasks,tests}/**/*.{js,jsx}" ] } + - + devDependencies: + - '**/tests.js' + - '**/stories.{js,jsx}' + - "./frontend/{stories,tasks,tests}/**/*.{js,jsx}" diff --git a/.storybook/config.js b/.storybook/config.js index cf24fab7bd..3932395745 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -6,9 +6,11 @@ import '../frontend/build/static/styles/experiments.css'; import '../frontend/build/static/styles/main.css'; const req = require.context('../frontend/stories', true, /\-story\.jsx?$/); +const reqInSrcTree = require.context('../frontend/src/app', true, /\/stories.jsx?$/); function loadStories() { req.keys().forEach((filename) => req(filename)); + reqInSrcTree.keys().forEach((filename) => reqInSrcTree(filename)); } configure(loadStories, module); diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js new file mode 100644 index 0000000000..46a73495a5 --- /dev/null +++ b/.storybook/webpack.config.js @@ -0,0 +1,23 @@ +const path = require('path'); + +// Export a function. Accept the base config as the only param. +module.exports = (storybookBaseConfig, configType) => { + // configType has a value of 'DEVELOPMENT' or 'PRODUCTION' + // You can change the configuration based on that. + // 'PRODUCTION' is used when building the static version of storybook. + + // Make whatever fine-grained changes you need + storybookBaseConfig.module.rules.push({ + test: /\.s?css$/, + loaders: ["style-loader", "css-loader", "sass-loader"], + include: path.resolve(__dirname, '../') + }); + + storybookBaseConfig.module.rules.push({ + test: /\.(png|jpg|gif|svg)$/, + loaders: ['url-loader'] + }); + + // Return the altered config + return storybookBaseConfig; +}; diff --git a/frontend/.flowconfig b/frontend/.flowconfig index 2f9a67ba3a..877195ee4d 100644 --- a/frontend/.flowconfig +++ b/frontend/.flowconfig @@ -11,4 +11,9 @@ [libs] ../flow-typed +[options] +module.file_ext=.scss +module.file_ext=.js +module.file_ext=.jsx +module.name_mapper='.*\.scss$' -> 'empty/object' diff --git a/frontend/tasks/pages.js b/frontend/tasks/pages.js index 73b26148b4..c66035a41f 100644 --- a/frontend/tasks/pages.js +++ b/frontend/tasks/pages.js @@ -196,6 +196,7 @@ function generateStaticPage(prepareForClient, pageName, pageParam, component, { + diff --git a/frontend/test-setup.js b/frontend/test-setup.js index 80c1c10577..a9fbf14f96 100644 --- a/frontend/test-setup.js +++ b/frontend/test-setup.js @@ -1,5 +1,8 @@ // Global setup for all tests +// HACK: Ignore non-JS imports used for asset dependencies in Webpack +require.extensions['.scss'] = function () {} + // We need jsdom for enzyme mount()'ed components - mainly the sticky header // scroll handler stuff on the experiments page. diff --git a/gulpfile.babel.js b/gulpfile.babel.js index df985d293f..9fc5bd2ad7 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -1,6 +1,9 @@ /* eslint-disable import/no-extraneous-dependencies*/ require('babel-polyfill'); +// HACK: Ignore non-JS imports used for asset dependencies in Webpack +require.extensions['.scss'] = function () {} + const gulp = require('gulp'); const config = require('./frontend/config.js'); diff --git a/package.json b/package.json index a9437fdde0..2b9c25fff4 100644 --- a/package.json +++ b/package.json @@ -55,14 +55,16 @@ "cross-spawn": "^5.1.0", "del": "2.2.2", "doctoc": "1.3.0", + "empty": "0.10.1", "enzyme": "2.7.1", - "eslint": "3.17.1", + "eslint": "3.19.0", "eslint-config-airbnb": "10.0.1", "eslint-config-react": "1.1.7", "eslint-plugin-flowtype": "2.30.0", "eslint-plugin-import": "2.2.0", "eslint-plugin-jsx-a11y": "5.0.3", "eslint-plugin-react": "7.0.1", + "extract-text-webpack-plugin": "3.0.0", "feed": "1.1.0", "fetch-mock": "5.9.4", "flow-bin": "0.47.0", @@ -95,11 +97,13 @@ "require-globify": "1.4.1", "run-sequence": "1.2.2", "sass-lint": "1.10.2", + "sass-loader": "6.0.6", "sinon": "2.0.0", "through2": "2.0.3", "uglifyjs-webpack-plugin": "^0.4.6", + "url-loader": "^0.5.9", "vinyl-source-stream": "1.1.0", - "webpack": "^3.5.5", + "webpack": "3.5.6", "webpack-bundle-analyzer": "^2.9.0", "webpack-stream": "^4.0.0", "yamljs": "0.2.8" @@ -122,11 +126,11 @@ "static": "gulp dist-build", "docs": "doctoc README.md && doctoc addon/README.md", "lint": "gulp scripts-lint styles-lint", - "test": "cd frontend && mocha --require babel-register --require test-setup.js --recursive", + "test": "cd frontend && mocha --require babel-register --require test-setup.js --recursive 'test' 'src/**/tests.js'", "flow": "flow frontend", "l10n:extract": "gulp content-extract-strings", "l10n:check": "npm run l10n:extract && git diff-files --quiet --ignore-all-space locales && echo 'No missing strings'", - "test:ci": "cross-env NODE_ENV=test nyc mocha --recursive --reporter mocha-junit-reporter --require frontend/test-setup.js frontend/test", + "test:ci": "cross-env NODE_ENV=test nyc mocha --recursive --reporter mocha-junit-reporter --require frontend/test-setup.js frontend/test 'frontend/src/**/tests.js'", "test:watch": "npm test -- --watch", "test:all": "npm run lint && npm run flow && npm run test && npm run flow-coverage && cd addon && npm run lint && npm run test && tox -e ui-tests", "coverage": "cross-env NODE_ENV=test nyc mocha --recursive --require frontend/test-setup.js frontend/test", diff --git a/webpack.config.js b/webpack.config.js index 29a98dfc8f..ad6171a02f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,6 +7,7 @@ const config = require('./frontend/config.js'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const ExtractTextPlugin = require('extract-text-webpack-plugin'); const RUN_ANALYZER = !!process.env.ANALYZER; const NODE_ENV = process.env.NODE_ENV || 'development'; @@ -35,6 +36,10 @@ const vendorModules = Object.keys(packageJSON.dependencies) .filter(name => excludeVendorModules.indexOf(name) < 0) .concat(includeVendorModules); +const extractSass = new ExtractTextPlugin({ + filename: '[name].css' +}); + const plugins = [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': `"${NODE_ENV}"`, @@ -46,7 +51,8 @@ const plugins = [ /moment[\/\\]locale$/, // eslint-disable-line no-useless-escape new RegExp(config.AVAILABLE_LOCALES.replace(/,/g, '|')) ), - new webpack.optimize.CommonsChunkPlugin('static/app/vendor.js') + new webpack.optimize.CommonsChunkPlugin('static/app/vendor.js'), + extractSass ]; if (!IS_DEV) { @@ -91,12 +97,23 @@ module.exports = { contentBase: 'dist' }, devtool: 'source-map', + resolve: { + extensions: ['.js', '.jsx'] + }, module: { - loaders: [ + rules: [ { test: /\.jsx?$/, exclude: /node_modules/, - loader: 'babel-loader' + use: 'babel-loader' + }, + { + test: /\.scss$/, + use: extractSass.extract({ + use: [{ loader: 'css-loader' }, { loader: 'sass-loader' }], + // use style-loader in development + fallback: 'style-loader' + }) } ] },