зеркало из https://github.com/mozilla/treeherder.git
Bug 1364894 - Upgrade from Neutrino 4 to 9 (#4216)
Neutrino controls our frontend linting, transpilation, source-maps, testing, dev-server and optimisation of production builds. Highlights of the upgrade are: * Major version updates to the individual tools within (such as webpack, Babel and ESLint), significantly improving performance, fixing transpilation/minification correctness bugs, adding support for newer ECMAScript features, and increasing linter coverage. * Hot reloading in the dev server now works for all entry-points and not just the jobs view, shortening the feedback cycle. * Reduced bundle size due to webpack 4's tree shaking, scope hoisting, automatic shared/vendor code chunk splitting (no need for the manually maintained 'vendor' list). * CSS is now extracted out of JS, which improves performance, reduces bundle size and prevents the initial white flash of un-styled content. * Support for dynamic imports/code splitting (needed for bug 1502192). * Support for Jest via a new Jest preset (unblocks bug 1364045). * Support for public class field declarations (unblocks bug 1480166). * Improved source-maps (increases the quality of production exception trace-backs and fixes several debugger breakpoint bugs). * Reduced amount of custom configuration required for our fairly complex frontend needs, reducing maintenance burden and allowing for easier future Neutrino upgrades. In addition this PR: * Fixes the WhiteNoise `immutable_file_test()` regex, so that it now correctly enables browser caching of images, fonts and source maps. * Enables webpack-dev-server's overlay feature, which displays any compilation errors in the browser, saving having to switch back to the console (this can be enabled for warnings too if desired). * Enables webpack-dev-server's automatic browser-opening feature, which saves having to manually navigate to `localhost:5000` after running `yarn start`. * Switches Karma tests to run Firefox in headless mode, reducing the workflow disruption when running `yarn test`. * Uses the new webpack `performance` option to enable maximum asset file size thresholds, to help prevent bundle-size regressions. * Rewrites the `package.json` script commands so that they now work correctly on Windows, even when setting environment variables. Performance comparison: * Local `yarn build`: - Cached: 2m34s -> 23s - Uncached: 2m34s -> 58s * Local `yarn start`: - Cached: 34.5s -> 13.6s - Uncached: 34.5s -> 31.3s * Local `yarn test` - Cached: 61.5s -> 19.8s - Uncached: 61.5s -> 22.0s * Local `yarn lint` - Cached: 3.8s -> 1.8s - Uncached: 13.7s -> 13.4s * Travis end-to-end time: 9 minutes -> 6 minutes * Heroku deploy end-to-end time: 14 minutes -> 9 minutes
This commit is contained in:
Родитель
cbd0a384eb
Коммит
565ae4c13e
71
.eslintrc.js
71
.eslintrc.js
|
@ -1,6 +1,65 @@
|
|||
const path = require('path');
|
||||
const Neutrino = require('neutrino');
|
||||
|
||||
const api = new Neutrino([path.resolve('./neutrino-custom/development.js')]);
|
||||
|
||||
module.exports = api.custom.eslintrc();
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: 'eslint-config-airbnb',
|
||||
parser: 'babel-eslint',
|
||||
settings: {
|
||||
react: {
|
||||
version: '16.6',
|
||||
},
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
jasmine: true,
|
||||
},
|
||||
rules: {
|
||||
// TODO: Fix & remove the majority of these deviations from AirBnB style (bug 1183749).
|
||||
camelcase: 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'consistent-return': 'off',
|
||||
'default-case': 'off',
|
||||
'func-names': 'off',
|
||||
'function-paren-newline': 'off',
|
||||
'implicit-arrow-linebreak': 'off',
|
||||
// Indentation is disabled pending a switch from 4 to 2 space for JS.
|
||||
indent: 'off',
|
||||
'jsx-a11y/anchor-is-valid': 'off',
|
||||
'jsx-a11y/click-events-have-key-events': 'off',
|
||||
'jsx-a11y/label-has-associated-control': 'off',
|
||||
'jsx-a11y/label-has-for': 'off',
|
||||
'jsx-a11y/no-noninteractive-element-interactions': 'off',
|
||||
'jsx-a11y/no-static-element-interactions': 'off',
|
||||
'max-len': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-continue': 'off',
|
||||
'no-else-return': 'off',
|
||||
'no-mixed-operators': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-prototype-builtins': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'object-curly-newline': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'operator-linebreak': 'off',
|
||||
'padded-blocks': 'off',
|
||||
'prefer-arrow-callback': 'off',
|
||||
'prefer-destructuring': 'off',
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
'prefer-template': 'off',
|
||||
radix: 'off',
|
||||
'react/button-has-type': 'off',
|
||||
'react/default-props-match-prop-types': 'off',
|
||||
'react/destructuring-assignment': 'off',
|
||||
'react/forbid-prop-types': 'off',
|
||||
'react/jsx-closing-tag-location': 'off',
|
||||
'react/jsx-one-expression-per-line': 'off',
|
||||
'react/jsx-wrap-multilines': 'off',
|
||||
'react/no-access-state-in-setstate': 'off',
|
||||
'react/no-multi-comp': 'off',
|
||||
'react/no-unused-state': 'off',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ npm-error.log
|
|||
|
||||
# Treeherder-specific
|
||||
_build/
|
||||
dist/
|
||||
build/
|
||||
treeherder/static/
|
||||
|
||||
# Celery
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
// `use strict` is still necessary here since this file is not treated as a module.
|
||||
'use strict'; // eslint-disable-line strict, lines-around-directive
|
||||
|
||||
const BACKEND = process.env.BACKEND || 'https://treeherder.mozilla.org';
|
||||
|
||||
module.exports = {
|
||||
options: {
|
||||
source: 'ui/',
|
||||
mains: {
|
||||
index: {
|
||||
entry: 'job-view/index.jsx',
|
||||
favicon: 'ui/img/tree_open.png',
|
||||
title: 'Treeherder',
|
||||
},
|
||||
logviewer: {
|
||||
entry: 'entry-logviewer.js',
|
||||
template: 'ui/logviewer.html',
|
||||
},
|
||||
userguide: {
|
||||
entry: 'userguide/index.jsx',
|
||||
favicon: 'ui/img/tree_open.png',
|
||||
title: 'Treeherder User Guide',
|
||||
},
|
||||
login: {
|
||||
entry: 'login-callback/index.jsx',
|
||||
title: 'Treeherder Login',
|
||||
},
|
||||
testview: {
|
||||
entry: 'test-view/index.jsx',
|
||||
title: 'Treeherder Test View',
|
||||
},
|
||||
perf: {
|
||||
entry: 'entry-perf.js',
|
||||
template: 'ui/perf.html',
|
||||
},
|
||||
'intermittent-failures': {
|
||||
entry: 'intermittent-failures/index.jsx',
|
||||
title: 'Intermittent Failures View',
|
||||
},
|
||||
},
|
||||
tests: 'tests/ui/',
|
||||
},
|
||||
use: [
|
||||
process.env.NODE_ENV === 'development' && ['@neutrinojs/eslint', {
|
||||
eslint: {
|
||||
// Treat ESLint errors as warnings so they don't block the webpack build.
|
||||
// Remove if/when changed in @neutrinojs/eslint.
|
||||
emitWarning: true,
|
||||
// We manage our lint config in .eslintrc.js instead of here.
|
||||
useEslintrc: true,
|
||||
},
|
||||
}],
|
||||
['@neutrinojs/react', {
|
||||
devServer: {
|
||||
historyApiFallback: false,
|
||||
open: !process.env.MOZ_HEADLESS,
|
||||
// Remove when enabled by default (https://github.com/neutrinojs/neutrino/issues/1131).
|
||||
overlay: true,
|
||||
proxy: {
|
||||
// Proxy any paths not recognised by webpack to the specified backend.
|
||||
'*': {
|
||||
changeOrigin: true,
|
||||
headers: {
|
||||
// Prevent Django CSRF errors, whilst still making it clear
|
||||
// that the requests were from local development.
|
||||
referer: `${BACKEND}/webpack-dev-server`,
|
||||
},
|
||||
target: BACKEND,
|
||||
onProxyRes: (proxyRes) => {
|
||||
// Strip the cookie `secure` attribute, otherwise production's cookies
|
||||
// will be rejected by the browser when using non-HTTPS localhost:
|
||||
// https://github.com/nodejitsu/node-http-proxy/pull/1166
|
||||
const removeSecure = str => str.replace(/; secure/i, '');
|
||||
const cookieHeader = proxyRes.headers['set-cookie'];
|
||||
if (cookieHeader) {
|
||||
proxyRes.headers['set-cookie'] = Array.isArray(cookieHeader)
|
||||
? cookieHeader.map(removeSecure)
|
||||
: removeSecure(cookieHeader);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
// Inside Vagrant filesystem watching has to be performed using polling mode,
|
||||
// since inotify doesn't work with Virtualbox shared folders.
|
||||
watchOptions: process.env.USE_WATCH_POLLING && {
|
||||
// Poll only once a second and ignore the node_modules folder to keep CPU usage down.
|
||||
poll: 1000,
|
||||
ignored: /node_modules/,
|
||||
},
|
||||
},
|
||||
devtool: {
|
||||
// Enable source maps for `yarn build` too (but not on CI, since it doubles build times).
|
||||
production: process.env.CI ? false : 'source-map',
|
||||
},
|
||||
style: {
|
||||
// Disable Neutrino's CSS modules support, since we don't use it.
|
||||
modules: false,
|
||||
},
|
||||
targets: {
|
||||
browsers: [
|
||||
'last 1 Chrome versions',
|
||||
'last 1 Edge versions',
|
||||
'last 1 Firefox versions',
|
||||
'last 1 Safari versions',
|
||||
],
|
||||
},
|
||||
}],
|
||||
['@neutrinojs/copy', {
|
||||
patterns: [
|
||||
'ui/contribute.json',
|
||||
'ui/revision.txt',
|
||||
'ui/robots.txt',
|
||||
],
|
||||
}],
|
||||
(neutrino) => {
|
||||
neutrino.config
|
||||
.plugin('provide')
|
||||
.use(require.resolve('webpack/lib/ProvidePlugin'), [{
|
||||
// Required since AngularJS and jquery.flot don't import jQuery themselves.
|
||||
jQuery: 'jquery',
|
||||
'window.jQuery': 'jquery',
|
||||
}]);
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// Fail the build if these file size thresholds (in bytes) are exceeded,
|
||||
// to help prevent unknowingly regressing the bundle size (bug 1384255).
|
||||
neutrino.config.performance
|
||||
.hints('error')
|
||||
.maxAssetSize(1.29 * 1024 * 1024)
|
||||
.maxEntrypointSize(1.63 * 1024 * 1024);
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
|
@ -15,8 +15,9 @@ matrix:
|
|||
install:
|
||||
- source ./bin/travis-setup.sh js_env
|
||||
script:
|
||||
# `yarn build` is tested as part of the Selenium job.
|
||||
- yarn lint
|
||||
- yarn test
|
||||
- yarn build
|
||||
|
||||
- env: python2-linters
|
||||
sudo: false
|
||||
|
|
|
@ -5,23 +5,16 @@
|
|||
set -euo pipefail
|
||||
|
||||
# Make the current Git revision accessible at <site-root>/revision.txt
|
||||
echo "$SOURCE_VERSION" > ui/revision.txt
|
||||
|
||||
# Create a `dist/` directory containing built/minified versions of the `ui/` assets.
|
||||
# Uses the node binaries/packages installed by the nodejs buildpack previously.
|
||||
yarn build
|
||||
echo "$SOURCE_VERSION" > build/revision.txt
|
||||
|
||||
# Generate gzipped versions of files that would benefit from compression, that
|
||||
# WhiteNoise can then serve in preference to the originals. This is required
|
||||
# since WhiteNoise's Django storage backend only gzips assets handled by
|
||||
# collectstatic, and so does not affect files in the `dist/` directory.
|
||||
python -m whitenoise.compress dist
|
||||
# collectstatic, and so does not affect files in the `build/` directory.
|
||||
python -m whitenoise.compress build
|
||||
|
||||
# Remove nodejs files to reduce slug size (and avoid environment variable
|
||||
# pollution from the nodejs profile script), since they are no longer
|
||||
# required once `yarn build` has run. The buildpack cache will still
|
||||
# contain them, so this doesn't slow down the next slug compile.
|
||||
rm -r .heroku/node/
|
||||
rm -r .heroku/yarn/
|
||||
rm -r .profile.d/nodejs.sh
|
||||
rm -r node_modules/
|
||||
# required after `yarn heroku-postbuild` has run. The buildpack cache will
|
||||
# still contain them, so this doesn't slow down the next slug compile.
|
||||
rm -r .heroku/node/ .heroku/yarn/ .profile.d/nodejs.sh node_modules/
|
||||
|
|
|
@ -47,7 +47,7 @@ production site. You do not need to set up the Vagrant VM unless making backend
|
|||
If you need to serve data from another domain, type:
|
||||
|
||||
```bash
|
||||
$ BACKEND_DOMAIN=<url> yarn start
|
||||
$ BACKEND=<url> yarn start
|
||||
```
|
||||
|
||||
This will run the unminified UI using ``<url>`` as the service domain.
|
||||
|
@ -153,8 +153,7 @@ Starting a local Treeherder instance
|
|||
vagrant ~/treeherder$ yarn start:local
|
||||
```
|
||||
|
||||
This will build the UI code in the ``dist/`` folder and keep watching for
|
||||
new changes.
|
||||
This will build the UI code and keep watching for new changes.
|
||||
|
||||
* Visit <http://localhost:5000> in your browser (NB: port has changed). Note: There will be no data to display until the ingestion tasks are run.
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// We're not using @neutrinojs/karma since we'd end up overriding most of it in
|
||||
// order to use Firefox instead of Chrome, jasmine instead of mocha, and so on.
|
||||
|
||||
const neutrino = require('neutrino');
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
const webpackConfig = neutrino().webpack();
|
||||
|
||||
// Skip building the entrypoints, since everything is imported in tests/ui/unit/init.js.
|
||||
delete webpackConfig.entry;
|
||||
|
||||
// Re-enable Buffer since Karma fails to work without it.
|
||||
webpackConfig.node.Buffer = true;
|
||||
|
||||
// Work around karma-webpack hanging under webpack 4:
|
||||
// https://github.com/webpack-contrib/karma-webpack/issues/322
|
||||
webpackConfig.optimization.splitChunks = false;
|
||||
webpackConfig.optimization.runtimeChunk = false;
|
||||
|
||||
module.exports = (config) => {
|
||||
config.set({
|
||||
plugins: [
|
||||
'karma-webpack',
|
||||
'karma-firefox-launcher',
|
||||
'karma-jasmine',
|
||||
],
|
||||
browsers: ['FirefoxHeadless'],
|
||||
frameworks: ['jasmine'],
|
||||
files: [
|
||||
'tests/ui/unit/init.js',
|
||||
{
|
||||
pattern: 'tests/ui/mock/**/*.json',
|
||||
watched: true,
|
||||
included: false,
|
||||
served: true,
|
||||
},
|
||||
],
|
||||
preprocessors: {
|
||||
'tests/ui/unit/init.js': ['webpack'],
|
||||
},
|
||||
webpack: webpackConfig,
|
||||
webpackMiddleware: {
|
||||
// Make the webpack compile output less verbose.
|
||||
stats: {
|
||||
all: false,
|
||||
errors: true,
|
||||
timings: true,
|
||||
warnings: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,326 +0,0 @@
|
|||
'use strict';
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const lintPreset = require('./lint');
|
||||
const reactPreset = require('neutrino-preset-react');
|
||||
const HtmlPlugin = require('html-webpack-plugin');
|
||||
const htmlTemplate = require('html-webpack-template');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const CWD = process.cwd();
|
||||
const SRC = path.join(CWD, 'src'); // neutrino's default source directory
|
||||
const UI = path.join(CWD, 'ui');
|
||||
const DIST = path.join(CWD, 'dist');
|
||||
const INDEX_TEMPLATE = path.join(UI, 'index.html');
|
||||
const PERF_TEMPLATE = path.join(UI, 'perf.html');
|
||||
const LOGVIEWER_TEMPLATE = path.join(UI, 'logviewer.html');
|
||||
|
||||
const HTML_MINIFY_OPTIONS = {
|
||||
useShortDoctype: true,
|
||||
keepClosingSlash: true,
|
||||
collapseWhitespace: true,
|
||||
preserveLineBreaks: true
|
||||
};
|
||||
|
||||
module.exports = neutrino => {
|
||||
reactPreset(neutrino);
|
||||
lintPreset(neutrino);
|
||||
|
||||
// Change the ouput path from build/ to dist/:
|
||||
neutrino.config.output.path(DIST);
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
// Include files from node_modules in the separate, more-cacheable vendor chunk:
|
||||
const jsDeps = [
|
||||
'auth0-js',
|
||||
'bootstrap',
|
||||
'hawk',
|
||||
'jquery',
|
||||
'jquery.scrollto',
|
||||
'js-yaml',
|
||||
'metrics-graphics',
|
||||
'mousetrap',
|
||||
'numeral',
|
||||
'prop-types',
|
||||
'react',
|
||||
'react-dom',
|
||||
'react-highlight-words',
|
||||
'react-select',
|
||||
'taskcluster-client-web',
|
||||
'taskcluster-lib-scopes'
|
||||
];
|
||||
jsDeps.map(dep =>
|
||||
neutrino.config.entry('vendor').add(dep)
|
||||
);
|
||||
}
|
||||
|
||||
// Neutrino looks for the entry at src/index.js by default; Delete this and add the index in ui/:
|
||||
neutrino.config
|
||||
.entry('index')
|
||||
.delete(path.join(SRC, 'index.js'))
|
||||
.add(path.join(UI, 'job-view', 'index.jsx'))
|
||||
.end();
|
||||
// Add several other treeherder entry points:
|
||||
neutrino.config
|
||||
.entry('perf')
|
||||
.add(path.join(UI, 'entry-perf.js'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('logviewer')
|
||||
.add(path.join(UI, 'entry-logviewer.js'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('login')
|
||||
.add(path.join(UI, 'login-callback', 'index.jsx'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('userguide')
|
||||
.add(path.join(UI, 'userguide', 'index.jsx'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('testview')
|
||||
.add(path.join(UI, 'test-view', 'index.jsx'))
|
||||
.end();
|
||||
neutrino.config
|
||||
.entry('intermittent-failures')
|
||||
.add(path.join(UI, 'intermittent-failures', 'index.jsx'))
|
||||
.end();
|
||||
|
||||
// Likewise, we must modify the include paths for the compile rule to look in ui/ instead of src/:
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('compile')
|
||||
.include(UI);
|
||||
|
||||
// Don't use file loader for html...
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v4.2.0/packages/neutrino-preset-web/src/index.js#L64-L69
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('html')
|
||||
.loaders.delete('file');
|
||||
// Instead, use html-loader, like Neutrino 8:
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v8.0.18/packages/html-loader/index.js#L7
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('html')
|
||||
.loader('html', require.resolve('html-loader'), {
|
||||
// Override html-loader's default of `img:src`,
|
||||
// so it also parses favicon images (`<link href="...">`).
|
||||
attrs: ['img:src', 'link:href']
|
||||
});
|
||||
|
||||
// Backport Neutrino 8's `test` regex, since Neutrino 4 omitted `.gif`:
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v4.2.0/packages/neutrino-preset-web/src/index.js#L108
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v8.0.18/packages/image-loader/index.js#L20
|
||||
// Fixes "You may need an appropriate loader to handle this file type" errors for `dancing_cat.gif`.
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('img')
|
||||
.test(/\.(png|jpg|jpeg|gif|webp)(\?v=\d+\.\d+\.\d+)?$/);
|
||||
|
||||
// Remove Neutrino 4's invalid SVG mimetype, and use auto-detection instead, like Neutrino 8.
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v4.2.0/packages/neutrino-preset-web/src/index.js#L98-L104
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/blob/v8.0.18/packages/image-loader/index.js#L11-L16
|
||||
// Fixes the log viewer icon on the job details panel (which url-loader embeds as a base64 encoded data URI).
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('svg')
|
||||
.loader('url', ({ options }) => {
|
||||
options.mimetype = null;
|
||||
return { options };
|
||||
});
|
||||
|
||||
// Set up templates for each entry point:
|
||||
neutrino.config.plugins.delete('html');
|
||||
neutrino.config
|
||||
.plugin('html-index')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
template: INDEX_TEMPLATE,
|
||||
chunks: ['index', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-perf')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'perf.html',
|
||||
template: PERF_TEMPLATE,
|
||||
chunks: ['perf', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-logviewer')
|
||||
.use(HtmlPlugin, {
|
||||
inject: 'body',
|
||||
filename: 'logviewer.html',
|
||||
template: LOGVIEWER_TEMPLATE,
|
||||
chunks: ['logviewer', 'vendor', 'manifest'],
|
||||
minify: HTML_MINIFY_OPTIONS
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-userguide')
|
||||
.use(HtmlPlugin, {
|
||||
inject: false,
|
||||
template: htmlTemplate,
|
||||
filename: 'userguide.html',
|
||||
favicon: 'ui/img/tree_open.png',
|
||||
chunks: ['userguide', 'vendor', 'manifest'],
|
||||
appMountId: 'root',
|
||||
xhtml: true,
|
||||
mobile: true,
|
||||
minify: HTML_MINIFY_OPTIONS,
|
||||
title: 'Treeherder User Guide'
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-login')
|
||||
.use(HtmlPlugin, {
|
||||
inject: false,
|
||||
template: htmlTemplate,
|
||||
filename: 'login.html',
|
||||
chunks: ['login', 'vendor', 'manifest'],
|
||||
appMountId: 'root',
|
||||
xhtml: true,
|
||||
mobile: true,
|
||||
minify: HTML_MINIFY_OPTIONS,
|
||||
title: 'Treeherder Login',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Treeherder Login',
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: 'Mozilla Treeherder',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-testview')
|
||||
.use(HtmlPlugin, {
|
||||
inject: false,
|
||||
template: htmlTemplate,
|
||||
filename: 'testview.html',
|
||||
chunks: ['testview', 'vendor', 'manifest'],
|
||||
appMountId: 'root',
|
||||
xhtml: true,
|
||||
mobile: true,
|
||||
minify: HTML_MINIFY_OPTIONS,
|
||||
title: "Treeherder TestView",
|
||||
meta: [
|
||||
{
|
||||
"name": "description",
|
||||
"content": "Treeherder TestView"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"content": "Mozilla Treeherder"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-index')
|
||||
.use(HtmlPlugin, {
|
||||
inject: false,
|
||||
template: htmlTemplate,
|
||||
filename: 'index.html',
|
||||
favicon: 'ui/img/tree_open.png',
|
||||
chunks: ['index', 'vendor', 'manifest'],
|
||||
appMountId: 'root',
|
||||
xhtml: true,
|
||||
mobile: true,
|
||||
minify: HTML_MINIFY_OPTIONS,
|
||||
title: 'Treeherder',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Treeherder',
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: 'Mozilla Treeherder',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
neutrino.config
|
||||
.plugin('html-intermittent-failures')
|
||||
.use(HtmlPlugin, {
|
||||
inject: false,
|
||||
template: htmlTemplate,
|
||||
filename: 'intermittent-failures.html',
|
||||
chunks: ['intermittent-failures', 'vendor', 'manifest'],
|
||||
appMountId: 'root',
|
||||
xhtml: true,
|
||||
mobile: true,
|
||||
minify: HTML_MINIFY_OPTIONS,
|
||||
title: "Treeherder Intermittent Failures",
|
||||
meta: [
|
||||
{
|
||||
"name": "description",
|
||||
"content": "Treeherder Intermittent Failures"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"content": "Mozilla Treeherder"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Adjust babel env to loosen up browser compatibility requirements
|
||||
neutrino.config
|
||||
.module
|
||||
.rule('compile')
|
||||
.loader('babel', ({ options }) => {
|
||||
options.presets[0][1].targets.browsers = [
|
||||
'last 1 Chrome versions',
|
||||
'last 1 Firefox versions',
|
||||
'last 1 Edge versions',
|
||||
'last 1 Safari versions'
|
||||
];
|
||||
// Work around a transform-regenerator bug that causes "Cannot read property '0' of null"
|
||||
// when encountering usages of async. See:
|
||||
// https://github.com/babel/babel/issues/4759
|
||||
options.presets[0][1].include = options.presets[0][1].include.filter(e => e !== 'transform-regenerator');
|
||||
return options;
|
||||
});
|
||||
|
||||
// Remove additional node_modules directories added by Neutrino that cause
|
||||
// incorrect module resolution, and restore the webpack defaults:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1465041
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/issues/822
|
||||
// https://webpack.js.org/configuration/resolve/#resolve-modules
|
||||
neutrino.config.resolve.modules.clear();
|
||||
neutrino.config.resolve.modules.add('node_modules');
|
||||
neutrino.config.resolveLoader.modules.clear();
|
||||
neutrino.config.resolveLoader.modules.add('node_modules');
|
||||
|
||||
neutrino.config
|
||||
.plugin('provide')
|
||||
.use(webpack.ProvidePlugin, {
|
||||
// Required since AngularJS and jquery.flot don't import jQuery themselves.
|
||||
jQuery: require.resolve('jquery'),
|
||||
'window.jQuery': require.resolve('jquery'),
|
||||
});
|
||||
|
||||
neutrino.config.devtool('source-map');
|
||||
|
||||
// Overwriting the existing copy rule rather than modifying, since it's only
|
||||
// enabled in production, whereas it really should be used in development too.
|
||||
neutrino.config.plugin('copy')
|
||||
.use(CopyPlugin, [
|
||||
{ context: UI, from: 'contribute.json' },
|
||||
{ context: UI, from: 'revision.txt' },
|
||||
{ context: UI, from: 'robots.txt' },
|
||||
]);
|
||||
};
|
||||
|
||||
module.exports.CWD = CWD;
|
||||
module.exports.UI = UI;
|
||||
module.exports.DIST = DIST;
|
|
@ -1,48 +0,0 @@
|
|||
'use strict';
|
||||
const basePreset = require('./base');
|
||||
const UI = require('./base').UI;
|
||||
|
||||
// Default to the production backend (is overridden to localhost when using start:local)
|
||||
const BACKEND_DOMAIN = process.env.BACKEND_DOMAIN || 'https://treeherder.mozilla.org';
|
||||
|
||||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
|
||||
// Make the dev server proxy any paths not recognised by webpack to the specified backend.
|
||||
neutrino.config.devServer
|
||||
.contentBase(UI)
|
||||
.set('proxy', {
|
||||
'*': {
|
||||
target: BACKEND_DOMAIN,
|
||||
changeOrigin: true,
|
||||
onProxyReq: (proxyReq) => {
|
||||
// Adjust the referrer to keep Django's CSRF protection happy, whilst
|
||||
// still making it clear that the requests were from local development.
|
||||
proxyReq.setHeader('referer', `${BACKEND_DOMAIN}/webpack-dev-server`);
|
||||
},
|
||||
onProxyRes: (proxyRes) => {
|
||||
// Strip the cookie `secure` attribute, otherwise prod cookies
|
||||
// will be rejected by the browser when using non-HTTPS localhost:
|
||||
// https://github.com/nodejitsu/node-http-proxy/pull/1166
|
||||
const removeSecure = str => str.replace(/; secure/i, '');
|
||||
const setCookie = proxyRes.headers['set-cookie'];
|
||||
if (setCookie) {
|
||||
const result = Array.isArray(setCookie)
|
||||
? setCookie.map(removeSecure)
|
||||
: removeSecure(setCookie);
|
||||
proxyRes.headers['set-cookie'] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.USE_WATCH_POLLING) {
|
||||
// Inside Vagrant filesystem watching has to be performed using polling mode,
|
||||
// since inotify doesn't work with Virtualbox shared folders.
|
||||
neutrino.config.devServer.set('watchOptions', {
|
||||
// Poll only once a second and ignore the node_modules folder to keep CPU usage down.
|
||||
poll: 1000,
|
||||
ignored: /node_modules/,
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,105 +0,0 @@
|
|||
// `use strict` is still necessary here since this file is not treated as a module.
|
||||
'use strict'; // eslint-disable-line strict, lines-around-directive
|
||||
|
||||
const merge = require('deepmerge');
|
||||
const lintBase = require('neutrino-lint-base');
|
||||
const path = require('path');
|
||||
|
||||
const CWD = process.cwd();
|
||||
const UI = path.join(CWD, 'ui');
|
||||
|
||||
module.exports = (neutrino) => {
|
||||
lintBase(neutrino);
|
||||
neutrino.config.module
|
||||
.rule('lint')
|
||||
.include(UI)
|
||||
.test(/\.jsx?$/)
|
||||
.loader('eslint', props => merge(props, {
|
||||
options: {
|
||||
plugins: ['react'],
|
||||
envs: ['browser', 'es6', 'commonjs', 'jasmine'],
|
||||
baseConfig: {
|
||||
extends: ['airbnb'],
|
||||
},
|
||||
rules: {
|
||||
// TODO: Fix & remove these deviations from AirBnB style (bug 1183749).
|
||||
camelcase: 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'consistent-return': 'off',
|
||||
'default-case': 'off',
|
||||
'func-names': 'off',
|
||||
// Indentation is disabled pending a switch from 4 to 2 space for JS.
|
||||
indent: 'off',
|
||||
'jsx-a11y/label-has-for': 'off',
|
||||
'jsx-a11y/no-noninteractive-element-interactions': 'off',
|
||||
'jsx-a11y/no-static-element-interactions': 'off',
|
||||
'max-len': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-continue': 'off',
|
||||
'no-loop-func': 'off',
|
||||
'no-mixed-operators': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-prototype-builtins': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'padded-blocks': 'off',
|
||||
'prefer-arrow-callback': 'off',
|
||||
'prefer-template': 'off',
|
||||
radix: 'off',
|
||||
'react/forbid-prop-types': 'off',
|
||||
'react/no-multi-comp': 'off',
|
||||
// Backport of:
|
||||
// https://github.com/airbnb/javascript/blob/eslint-config-airbnb-v17.1.0/packages/eslint-config-airbnb/rules/react.js#L230-L272
|
||||
// Remove once we're on eslint-config-airbnb v17.
|
||||
'react/sort-comp': ['error', {
|
||||
order: [
|
||||
'static-methods',
|
||||
'instance-variables',
|
||||
'lifecycle',
|
||||
'/^on.+$/',
|
||||
'getters',
|
||||
'setters',
|
||||
'/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/',
|
||||
'instance-methods',
|
||||
'everything-else',
|
||||
'rendering',
|
||||
],
|
||||
groups: {
|
||||
lifecycle: [
|
||||
'displayName',
|
||||
'propTypes',
|
||||
'contextTypes',
|
||||
'childContextTypes',
|
||||
'mixins',
|
||||
'statics',
|
||||
'defaultProps',
|
||||
'constructor',
|
||||
'getDefaultProps',
|
||||
'getInitialState',
|
||||
'state',
|
||||
'getChildContext',
|
||||
'componentWillMount',
|
||||
'componentDidMount',
|
||||
'componentWillReceiveProps',
|
||||
'shouldComponentUpdate',
|
||||
'componentWillUpdate',
|
||||
'componentDidUpdate',
|
||||
'componentWillUnmount',
|
||||
],
|
||||
rendering: [
|
||||
'/^render.+$/',
|
||||
'render',
|
||||
],
|
||||
},
|
||||
}],
|
||||
},
|
||||
// Remove once we're on newer ESLint that has the updated browser globals list.
|
||||
globals: ['AbortController'],
|
||||
},
|
||||
}));
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
'use strict';
|
||||
const basePreset = require('./base');
|
||||
const CleanPlugin = require('clean-webpack-plugin');
|
||||
const CWD = require('./base').CWD;
|
||||
const DIST = require('./base').DIST;
|
||||
|
||||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
|
||||
neutrino.config.plugin('minify')
|
||||
.inject(BabiliPlugin => new BabiliPlugin({
|
||||
evaluate: false, // prevents some minification errors
|
||||
// Prevents a minification error in react-dom that manifests as
|
||||
// `ReferenceError: Hp is not defined` when loading the main jobs view (bug 1426902).
|
||||
// TODO: Either remove this workaround or file upstream if this persists
|
||||
// after the Neutrino upgrade (which comes with latest babel-plugin-minify-mangle-names).
|
||||
mangle: {
|
||||
keepFnName: true,
|
||||
},
|
||||
}
|
||||
));
|
||||
|
||||
neutrino.config.plugin('clean')
|
||||
.use(CleanPlugin, [DIST], { root: CWD } );
|
||||
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
'use strict';
|
||||
const basePreset = require('./base');
|
||||
const karmaPreset = require('neutrino-preset-karma');
|
||||
|
||||
module.exports = neutrino => {
|
||||
basePreset(neutrino);
|
||||
karmaPreset(neutrino);
|
||||
|
||||
// Normal karma config
|
||||
neutrino.custom.karma = {
|
||||
browsers: ['Firefox'],
|
||||
plugins: [
|
||||
require.resolve('karma-webpack'),
|
||||
require.resolve('karma-firefox-launcher'),
|
||||
require.resolve('karma-jasmine'),
|
||||
],
|
||||
frameworks: ['jasmine'],
|
||||
files: [
|
||||
'tests/ui/unit/init.js',
|
||||
{ pattern: 'tests/ui/mock/**/*.json', watched: true, served: true, included: false }
|
||||
],
|
||||
preprocessors: {
|
||||
'tests/ui/unit/init.js': ['webpack'],
|
||||
},
|
||||
};
|
||||
};
|
46
package.json
46
package.json
|
@ -14,6 +14,8 @@
|
|||
"d3": "5.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@neutrinojs/copy": "9.0.0-beta.1",
|
||||
"@neutrinojs/react": "9.0.0-beta.1",
|
||||
"@types/angular": "*",
|
||||
"@types/prop-types": "*",
|
||||
"@types/react": "*",
|
||||
|
@ -28,15 +30,8 @@
|
|||
"auth0-js": "9.8.1",
|
||||
"bootstrap": "4.1.3",
|
||||
"d3": "5.7.0",
|
||||
"deepmerge": "1.5.2",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-config-airbnb": "15.1.0",
|
||||
"eslint-plugin-import": "2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "5.1.1",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"font-awesome": "4.7.0",
|
||||
"history": "4.7.2",
|
||||
"html-loader": "0.5.5",
|
||||
"jquery": "3.3.1",
|
||||
"jquery.flot": "0.8.3",
|
||||
"jquery.scrollto": "2.1.2",
|
||||
|
@ -48,9 +43,7 @@
|
|||
"metrics-graphics": "2.15.6",
|
||||
"moment": "2.22.2",
|
||||
"mousetrap": "1.6.2",
|
||||
"neutrino": "4.3.1",
|
||||
"neutrino-lint-base": "4.3.1",
|
||||
"neutrino-preset-react": "4.2.3",
|
||||
"neutrino": "9.0.0-beta.1",
|
||||
"ng-text-truncate-2": "1.0.1",
|
||||
"numeral": "2.0.6",
|
||||
"popper.js": "1.14.4",
|
||||
|
@ -61,7 +54,7 @@
|
|||
"react-dom": "16.6.0",
|
||||
"react-fontawesome": "1.6.1",
|
||||
"react-highlight-words": "0.14.0",
|
||||
"react-hot-loader": "3.1.3",
|
||||
"react-hot-loader": "4.3.12",
|
||||
"react-hotkeys": "1.1.4",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-redux": "5.1.0",
|
||||
|
@ -76,27 +69,38 @@
|
|||
"redux-debounce": "1.0.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"taskcluster-client-web": "8.0.4",
|
||||
"taskcluster-lib-scopes": "10.0.1"
|
||||
"taskcluster-lib-scopes": "10.0.1",
|
||||
"webpack": "4.24.0",
|
||||
"webpack-cli": "3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@neutrinojs/eslint": "9.0.0-beta.1",
|
||||
"angular-mocks": "1.7.5",
|
||||
"enzyme": "3.7.0",
|
||||
"enzyme-adapter-react-16": "1.6.0",
|
||||
"eslint": "5.8.0",
|
||||
"eslint-config-airbnb": "17.1.0",
|
||||
"eslint-plugin-import": "2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "6.1.2",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"fetch-mock": "7.2.2",
|
||||
"jasmine-core": "3.3.0",
|
||||
"jasmine-jquery": "2.1.1",
|
||||
"karma": "1.7.1",
|
||||
"karma": "3.1.1",
|
||||
"karma-firefox-launcher": "1.1.0",
|
||||
"karma-jasmine": "1.1.2",
|
||||
"neutrino-preset-karma": "4.2.1"
|
||||
"karma-webpack": "4.0.0-beta.0",
|
||||
"webpack-dev-server": "3.1.10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./node_modules/neutrino/bin/neutrino build --presets ./neutrino-custom/production.js",
|
||||
"lint": "node ./node_modules/eslint/bin/eslint.js --cache --ext js,jsx \".*.js\" \"*.js\" ui/ tests/ui/ neutrino-custom/lint.js",
|
||||
"start": "node ./node_modules/neutrino/bin/neutrino start --presets ./neutrino-custom/development.js",
|
||||
"start:local": "BACKEND_DOMAIN=http://localhost:8000 node ./node_modules/neutrino/bin/neutrino start --presets ./neutrino-custom/development.js",
|
||||
"start:stage": "BACKEND_DOMAIN=https://treeherder.allizom.org node ./node_modules/neutrino/bin/neutrino start --presets ./neutrino-custom/development.js",
|
||||
"test": "node ./node_modules/neutrino/bin/neutrino test --presets ./neutrino-custom/test.js",
|
||||
"test:watch": "node ./node_modules/neutrino/bin/neutrino test --watch --presets ./neutrino-custom/test.js"
|
||||
"build": "node ./node_modules/webpack/bin/webpack.js --mode production",
|
||||
"build:dev": "node ./node_modules/webpack/bin/webpack.js --mode development",
|
||||
"heroku-postbuild": "yarn build",
|
||||
"lint": "node ./node_modules/eslint/bin/eslint.js --cache --report-unused-disable-directives --format codeframe --ext js,jsx \".*.js\" \"*.js\" ui/ tests/ui/",
|
||||
"start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode development",
|
||||
"start:local": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode development --env.BACKEND=http://localhost:8000",
|
||||
"start:stage": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode development --env.BACKEND=https://treeherder.allizom.org",
|
||||
"test": "node ./node_modules/karma/bin/karma start --single-run",
|
||||
"test:watch": "node ./node_modules/karma/bin/karma start"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,7 @@
|
|||
"@types/prop-types",
|
||||
"@types/react",
|
||||
"@types/react-dom",
|
||||
"@uirouter/angularjs",
|
||||
"deepmerge",
|
||||
"eslint",
|
||||
"eslint-config-airbnb",
|
||||
"eslint-plugin-jsx-a11y",
|
||||
"html-loader",
|
||||
"karma",
|
||||
"neutrino",
|
||||
"neutrino-lint-base",
|
||||
"neutrino-preset-karma",
|
||||
"neutrino-preset-react",
|
||||
"react-hot-loader"
|
||||
"@uirouter/angularjs"
|
||||
],
|
||||
"reviewers": [
|
||||
"@mozilla/treeherder-admins"
|
||||
|
|
|
@ -32,7 +32,7 @@ filterwarnings =
|
|||
error
|
||||
ignore::ImportWarning
|
||||
ignore::PendingDeprecationWarning
|
||||
# WhiteNoise warns if either `treeherder/static/` or `dist/` do not exist at startup,
|
||||
# WhiteNoise warns if either `treeherder/static/` or `build/` do not exist at startup,
|
||||
# however this is expected when running tests since Django collectstatic and yarn build
|
||||
# (which create those directories) typically aren't run apart from during deployments.
|
||||
ignore:No directory at.*:UserWarning:whitenoise.base
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import pytest
|
||||
|
||||
from treeherder.middleware import CustomWhiteNoise
|
||||
|
||||
URLS_IMMUTABLE = [
|
||||
# Assets generated by Neutrino.
|
||||
'/assets/2.379789df.css',
|
||||
'/assets/dancing_cat.fa5552a5.gif',
|
||||
'/assets/fontawesome-webfont.af7ae505.woff2',
|
||||
'/assets/fontawesome-webfont.fee66e71.woff',
|
||||
'/assets/index.1d85033a.js',
|
||||
'/assets/index.1d85033a.js.map',
|
||||
'/assets/perf.d7fea1e4.css',
|
||||
'/assets/perf.d7fea1e4.css.map',
|
||||
'/assets/treeherder-logo.3df97cff.png',
|
||||
]
|
||||
|
||||
URLS_NOT_IMMUTABLE = [
|
||||
'/',
|
||||
'/contribute.json',
|
||||
'/perf.html',
|
||||
'/revision.txt',
|
||||
'/tree_open.png',
|
||||
'/docs/schema.js',
|
||||
# The unhashed Neutrino/webpack output if using `yarn build --mode development`.
|
||||
'/assets/runtime.js',
|
||||
'/assets/vendors~index.js',
|
||||
# The unhashed Django static asset originals (used in development).
|
||||
'/static/debug_toolbar/assets/toolbar.css',
|
||||
'/static/rest_framework/docs/js/jquery.json-view.min.js',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url', URLS_IMMUTABLE)
|
||||
def test_immutable_file_test_matches(url):
|
||||
assert CustomWhiteNoise().immutable_file_test('', url)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url', URLS_NOT_IMMUTABLE)
|
||||
def test_immutable_file_test_does_not_match(url):
|
||||
assert not CustomWhiteNoise().immutable_file_test('', url)
|
|
@ -1,6 +1,5 @@
|
|||
// Karma/webpack entry for tests
|
||||
|
||||
import jQuery from 'jquery';
|
||||
// Manually import angular since angular-mocks doesn't do so itself
|
||||
import 'angular';
|
||||
import 'angular-mocks';
|
||||
|
@ -8,11 +7,6 @@ import 'jasmine-jquery';
|
|||
import { configure } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
// Global variables are set here instead of with webpack.ProvidePlugin
|
||||
// because neutrino removes plugin definitions for karma runs:
|
||||
// https://github.com/mozilla-neutrino/neutrino-dev/issues/617
|
||||
window.jQuery = jQuery;
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
const jsContext = require.context('../../../ui/js', true, /^\.\/.*\.jsx?$/);
|
||||
|
|
|
@ -386,7 +386,7 @@ REST_FRAMEWORK = {
|
|||
|
||||
# Whitenoise
|
||||
# Files in this directory will be served by WhiteNoise at the site root.
|
||||
WHITENOISE_ROOT = os.path.join(PROJECT_DIR, "..", "dist")
|
||||
WHITENOISE_ROOT = os.path.join(PROJECT_DIR, "..", "build")
|
||||
# Serve index.html for URLs ending in a trailing slash.
|
||||
WHITENOISE_INDEX_FILE = True
|
||||
# Only output the hashed filename version of static files and not the originals.
|
||||
|
|
|
@ -6,15 +6,20 @@ from whitenoise.middleware import WhiteNoiseMiddleware
|
|||
|
||||
|
||||
class CustomWhiteNoise(WhiteNoiseMiddleware):
|
||||
"""Sets long max-age headers for webpack generated files."""
|
||||
"""Sets long max-age headers for Neutrino-generated hashed files."""
|
||||
|
||||
# Matches webpack's style of chunk filenames. eg:
|
||||
# index.f03882a6258f16fceb70.bundle.js
|
||||
IMMUTABLE_FILE_RE = re.compile(r'\.[a-f0-9]{16,}\.bundle\.(js|css)$')
|
||||
# Matches Neutrino's style of hashed filename URLs, eg:
|
||||
# /assets/index.1d85033a.js
|
||||
# /assets/2.379789df.css.map
|
||||
# /assets/fontawesome-webfont.af7ae505.woff2
|
||||
IMMUTABLE_FILE_RE = re.compile(r'^/assets/.*\.[a-f0-9]{8}\..*')
|
||||
|
||||
def immutable_file_test(self, path, url):
|
||||
"""Support webpack bundle filenames when setting long max-age headers."""
|
||||
if self.IMMUTABLE_FILE_RE.search(url):
|
||||
"""
|
||||
Determines whether the given URL represents an immutable file (i.e. a file with a
|
||||
hash of its contents as part of its name) which can therefore be cached forever.
|
||||
"""
|
||||
if self.IMMUTABLE_FILE_RE.match(url):
|
||||
return True
|
||||
# Otherwise fall back to the default method, so we catch filenames in the
|
||||
# style output by GzipManifestStaticFilesStorage during collectstatic. eg:
|
||||
|
|
|
@ -22,9 +22,6 @@ import './css/treeherder-loading-overlay.css';
|
|||
// Bootstrap the Angular modules against which everything will be registered
|
||||
import './js/perf';
|
||||
|
||||
// React UI
|
||||
import './shared/auth/Login';
|
||||
|
||||
// Perf JS
|
||||
import './js/filters';
|
||||
import './js/models/perf/issue_tracker';
|
||||
|
|
|
@ -272,7 +272,7 @@ export const phTimeRangeValues = {
|
|||
export const phDefaultFramework = 'talos';
|
||||
|
||||
export const phFrameworksWithRelatedBranches = [
|
||||
1, // talos
|
||||
1, // talos
|
||||
10, // raptor
|
||||
11, // js-bench
|
||||
12, // devtools
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom';
|
||||
import { hot } from 'react-hot-loader';
|
||||
|
||||
import MainView from './MainView';
|
||||
import BugDetailsView from './BugDetailsView';
|
||||
|
@ -57,4 +58,4 @@ class App extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default hot(module)(App);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
import 'react-table/react-table.css';
|
||||
|
@ -9,16 +8,4 @@ import '../css/treeherder-global.css';
|
|||
import '../css/intermittent-failures.css';
|
||||
import App from './App';
|
||||
|
||||
function load() {
|
||||
render((
|
||||
<AppContainer>
|
||||
<App />
|
||||
</AppContainer>
|
||||
), document.getElementById('root'));
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./App', () => load(App));
|
||||
}
|
||||
render(<App />, document.getElementById('root'));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import SplitPane from 'react-split-pane';
|
||||
|
||||
import { thFavicons } from '../helpers/constants';
|
||||
|
@ -313,4 +314,4 @@ class App extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default hot(module)(App);
|
||||
|
|
|
@ -174,5 +174,5 @@ ActiveFilters.propTypes = {
|
|||
filterBarFilters: PropTypes.array.isRequired,
|
||||
isFieldFilterVisible: PropTypes.bool.isRequired,
|
||||
toggleFieldFilterVisible: PropTypes.func.isRequired,
|
||||
classificationTypes: PropTypes.array.isRequired, // eslint-disable-line react/no-unused-prop-types
|
||||
classificationTypes: PropTypes.array.isRequired,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
// Vendor Styles
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
@ -25,14 +24,4 @@ import '../css/treeherder-loading-overlay.css';
|
|||
|
||||
import App from './App';
|
||||
|
||||
const load = () => render((
|
||||
<AppContainer>
|
||||
<App />
|
||||
</AppContainer>
|
||||
), document.getElementById('root'));
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./App', load);
|
||||
}
|
||||
|
||||
load();
|
||||
render(<App />, document.getElementById('root'));
|
||||
|
|
|
@ -168,7 +168,7 @@ class PushJobs extends React.Component {
|
|||
|
||||
PushJobs.propTypes = {
|
||||
push: PropTypes.object.isRequired,
|
||||
platforms: PropTypes.array.isRequired, // eslint-disable-line react/no-unused-prop-types
|
||||
platforms: PropTypes.array.isRequired,
|
||||
repoName: PropTypes.string.isRequired,
|
||||
filterModel: PropTypes.object.isRequired,
|
||||
togglePinJob: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import Icon from 'react-fontawesome';
|
||||
import { hot } from 'react-hot-loader';
|
||||
|
||||
import AuthService from '../shared/auth/AuthService';
|
||||
import { webAuth, parseHash } from '../helpers/auth';
|
||||
|
@ -74,4 +75,4 @@ class LoginCallback extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
export default LoginCallback;
|
||||
export default hot(module)(LoginCallback);
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
|
||||
import LoginCallback from './LoginCallback';
|
||||
import '../css/login.css';
|
||||
|
||||
const load = () => render((
|
||||
<AppContainer>
|
||||
<LoginCallback />
|
||||
</AppContainer>
|
||||
), document.getElementById('root'));
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./LoginCallback', load);
|
||||
}
|
||||
|
||||
load();
|
||||
render(<LoginCallback />, document.getElementById('root'));
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
|
||||
|
@ -9,16 +8,8 @@ import '../css/treeherder-test-view.css';
|
|||
import { store, actions } from './redux/store';
|
||||
import App from './ui/App';
|
||||
|
||||
const load = () => render((
|
||||
<AppContainer>
|
||||
<Provider store={store}>
|
||||
<App actions={actions} />
|
||||
</Provider>
|
||||
</AppContainer>
|
||||
render((
|
||||
<Provider store={store}>
|
||||
<App actions={actions} />
|
||||
</Provider>
|
||||
), document.getElementById('root'));
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./ui/App', load);
|
||||
}
|
||||
|
||||
load();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import Navigation from './Navigation';
|
||||
|
@ -41,4 +42,4 @@ App.defaultProps = {
|
|||
location: null,
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default hot(module)(App);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
|
||||
import UserGuideHeader from './UserGuideHeader';
|
||||
import UserGuideBody from './UserGuideBody';
|
||||
|
@ -14,4 +15,4 @@ const App = () => (
|
|||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
export default hot(module)(App);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
|
@ -11,14 +10,4 @@ import '../css/treeherder-job-buttons.css';
|
|||
|
||||
import App from './App';
|
||||
|
||||
const load = () => render((
|
||||
<AppContainer>
|
||||
<App />
|
||||
</AppContainer>
|
||||
), document.getElementById('root'));
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./App', load);
|
||||
}
|
||||
|
||||
load();
|
||||
render(<App />, document.getElementById('root'));
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
const neutrino = require('neutrino');
|
||||
|
||||
module.exports = (env = {}) => {
|
||||
// Convert `--env.NAME <value>` CLI arguments into environment variables.
|
||||
// This makes it possible to write cross-platform-compatible package.json `scripts`.
|
||||
Object.entries(env).forEach(([name, value]) => {
|
||||
process.env[name] = value;
|
||||
});
|
||||
return neutrino().webpack();
|
||||
};
|
5785
yarn.lock
5785
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче