Commit a2323797 by Brian Jacobel

Work on sandbox bundling for webpack

parent 3b43fb38
...@@ -27,9 +27,6 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import ...@@ -27,9 +27,6 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
######################### Testing overrides #################################### ######################### Testing overrides ####################################
# Needed for the reset database management command
INSTALLED_APPS += ('django_extensions',)
# Redirect to the test_root folder within the repo # Redirect to the test_root folder within the repo
TEST_ROOT = REPO_ROOT / "test_root" TEST_ROOT = REPO_ROOT / "test_root"
GITHUB_REPO_ROOT = (TEST_ROOT / "data").abspath() GITHUB_REPO_ROOT = (TEST_ROOT / "data").abspath()
......
...@@ -861,6 +861,7 @@ INSTALLED_APPS = ( ...@@ -861,6 +861,7 @@ INSTALLED_APPS = (
# Maintenance tools # Maintenance tools
'maintenance', 'maintenance',
'django_extensions',
# Tracking # Tracking
'track', 'track',
......
...@@ -170,7 +170,7 @@ function junitSettings(config) { ...@@ -170,7 +170,7 @@ function junitSettings(config) {
* @param {String} pattern * @param {String} pattern
* @return {String} * @return {String}
*/ */
// I'd like to change fix the no-shadow violation on the next line, but it would break this shared conf's API. // I'd like to fix the no-shadow violation on the next line, but it would break this shared conf's API.
function defaultNormalizeFunc(appRoot, pattern) { // eslint-disable-line no-shadow function defaultNormalizeFunc(appRoot, pattern) { // eslint-disable-line no-shadow
if (pattern.match(/^common\/js/)) { if (pattern.match(/^common\/js/)) {
pattern = path.join(appRoot, '/common/static/' + pattern); pattern = path.join(appRoot, '/common/static/' + pattern);
......
...@@ -121,6 +121,7 @@ with open(CONFIG_ROOT / CONFIG_PREFIX + "env.json") as env_file: ...@@ -121,6 +121,7 @@ with open(CONFIG_ROOT / CONFIG_PREFIX + "env.json") as env_file:
STATIC_ROOT_BASE = ENV_TOKENS.get('STATIC_ROOT_BASE', None) STATIC_ROOT_BASE = ENV_TOKENS.get('STATIC_ROOT_BASE', None)
if STATIC_ROOT_BASE: if STATIC_ROOT_BASE:
STATIC_ROOT = path(STATIC_ROOT_BASE) STATIC_ROOT = path(STATIC_ROOT_BASE)
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
# STATIC_URL_BASE specifies the base url to use for static files # STATIC_URL_BASE specifies the base url to use for static files
......
...@@ -134,7 +134,6 @@ ...@@ -134,7 +134,6 @@
"SERVER_EMAIL": "devops@example.com", "SERVER_EMAIL": "devops@example.com",
"SESSION_COOKIE_DOMAIN": null, "SESSION_COOKIE_DOMAIN": null,
"SITE_NAME": "localhost:8003", "SITE_NAME": "localhost:8003",
"STATIC_ROOT_BASE": "** OVERRIDDEN **",
"STATIC_URL_BASE": "/static/", "STATIC_URL_BASE": "/static/",
"SUPPORT_SITE_LINK": "https://support.example.com", "SUPPORT_SITE_LINK": "https://support.example.com",
"SYSLOG_SERVER": "", "SYSLOG_SERVER": "",
......
...@@ -31,9 +31,6 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import ...@@ -31,9 +31,6 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
######################### Testing overrides #################################### ######################### Testing overrides ####################################
# Needed for the reset database management command
INSTALLED_APPS += ('django_extensions',)
# Redirect to the test_root folder within the repo # Redirect to the test_root folder within the repo
GITHUB_REPO_ROOT = (TEST_ROOT / "data").abspath() GITHUB_REPO_ROOT = (TEST_ROOT / "data").abspath()
LOG_DIR = (TEST_ROOT / "log").abspath() LOG_DIR = (TEST_ROOT / "log").abspath()
......
...@@ -1757,11 +1757,10 @@ REQUIRE_JS_PATH_OVERRIDES = { ...@@ -1757,11 +1757,10 @@ REQUIRE_JS_PATH_OVERRIDES = {
WEBPACK_LOADER = { WEBPACK_LOADER = {
'DEFAULT': { 'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/', 'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(REPO_ROOT, 'webpack-stats.json'), 'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json')
} }
} }
########################## DJANGO DEBUG TOOLBAR ############################### ########################## DJANGO DEBUG TOOLBAR ###############################
# We don't enable Django Debug Toolbar universally, but whenever we do, we want # We don't enable Django Debug Toolbar universally, but whenever we do, we want
...@@ -2037,6 +2036,7 @@ INSTALLED_APPS = ( ...@@ -2037,6 +2036,7 @@ INSTALLED_APPS = (
'django.contrib.admin', # only used in DEBUG mode 'django.contrib.admin', # only used in DEBUG mode
'django_nose', 'django_nose',
'debug', 'debug',
'django_extensions',
# Discussion forums # Discussion forums
'django_comment_client', 'django_comment_client',
......
...@@ -111,6 +111,7 @@ NOSE_PLUGINS = [ ...@@ -111,6 +111,7 @@ NOSE_PLUGINS = [
TEST_ROOT = path("test_root") TEST_ROOT = path("test_root")
# Want static files in the same dir for running on jenkins. # Want static files in the same dir for running on jenkins.
STATIC_ROOT = TEST_ROOT / "staticfiles" STATIC_ROOT = TEST_ROOT / "staticfiles"
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
STATUS_MESSAGE_PATH = TEST_ROOT / "status_message.json" STATUS_MESSAGE_PATH = TEST_ROOT / "status_message.json"
......
...@@ -53,6 +53,7 @@ LOG_DIR = (TEST_ROOT / "log").abspath() ...@@ -53,6 +53,7 @@ LOG_DIR = (TEST_ROOT / "log").abspath()
# Store the static files under test root so that they don't overwrite existing static assets # Store the static files under test root so that they don't overwrite existing static assets
STATIC_ROOT = (TEST_ROOT / "staticfiles" / "lms").abspath() STATIC_ROOT = (TEST_ROOT / "staticfiles" / "lms").abspath()
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
# Disable uglify when tests are running (used by build.js). # Disable uglify when tests are running (used by build.js).
# 1. Uglify is by far the slowest part of the build process # 1. Uglify is by far the slowest part of the build process
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* done. * done.
*/ */
modules: getModulesList([ modules: getModulesList([
'course_bookmarks/js/course_bookmarks_factory',, 'course_bookmarks/js/course_bookmarks_factory',
'discussion/js/discussion_board_factory', 'discussion/js/discussion_board_factory',
'discussion/js/discussion_profile_page_factory', 'discussion/js/discussion_profile_page_factory',
'js/api_admin/catalog_preview_factory', 'js/api_admin/catalog_preview_factory',
......
/* globals Logger */ /* globals Logger */
// import constants from 'edx-ui-toolkit/src/js/utils/constants'; import { keys } from 'edx-ui-toolkit/src/js/utils/constants';
// @TODO: Figure out how to make webpack handle default exports when libraryTarget: 'window' // @TODO: Figure out how to make webpack handle default exports when libraryTarget: 'window'
export class CourseOutline { // eslint-disable-line import/prefer-default-export export class CourseOutline { // eslint-disable-line import/prefer-default-export
...@@ -11,11 +11,11 @@ export class CourseOutline { // eslint-disable-line import/prefer-default-expor ...@@ -11,11 +11,11 @@ export class CourseOutline { // eslint-disable-line import/prefer-default-expor
const index = focusable.indexOf(event.target); const index = focusable.indexOf(event.target);
switch (event.key) { // eslint-disable-line default-case switch (event.key) { // eslint-disable-line default-case
case 'ArrowDown': // @TODO: Get these from the UI Toolkit case keys.down:
event.preventDefault(); event.preventDefault();
focusable[Math.min(index + 1, focusable.length - 1)].focus(); focusable[Math.min(index + 1, focusable.length - 1)].focus();
break; break;
case 'ArrowUp': // @TODO: Get these from the UI Toolkit case keys.up: // @TODO: Get these from the UI Toolkit
event.preventDefault(); event.preventDefault();
focusable[Math.max(index - 1, 0)].focus(); focusable[Math.max(index - 1, 0)].focus();
break; break;
......
/* globals Logger, loadFixtures */ /* globals Logger, loadFixtures */
// import constants from 'edx-ui-toolkit/src/js/utils/constants'; import { keys } from 'edx-ui-toolkit/src/js/utils/constants';
import { CourseOutline } from '../CourseOutline'; import { CourseOutline } from '../CourseOutline';
describe('Course outline factory', () => { describe('Course Outline factory', () => {
let outline; // eslint-disable-line no-unused-vars let outline; // eslint-disable-line no-unused-vars
// Our block IDs are invalid DOM selectors unless we first escape `:`, `+` and `@` // Our block IDs are invalid DOM selectors unless we first escape `:`, `+` and `@`
...@@ -40,7 +40,7 @@ describe('Course outline factory', () => { ...@@ -40,7 +40,7 @@ describe('Course outline factory', () => {
const current = document.querySelector(outlineIds.homeworkLabsAndDemos); const current = document.querySelector(outlineIds.homeworkLabsAndDemos);
const destination = document.querySelector(outlineIds.homeworkEssays); const destination = document.querySelector(outlineIds.homeworkEssays);
triggerKeyListener(current, destination, 'ArrowDown'); // @TODO: Get these from the UI Toolkit triggerKeyListener(current, destination, keys.down);
expect(destination.focus).toHaveBeenCalled(); expect(destination.focus).toHaveBeenCalled();
}); });
...@@ -49,7 +49,7 @@ describe('Course outline factory', () => { ...@@ -49,7 +49,7 @@ describe('Course outline factory', () => {
const current = document.querySelector(outlineIds.exampleWeek3BeSocial); const current = document.querySelector(outlineIds.exampleWeek3BeSocial);
const destination = document.querySelector(`${outlineIds.exampleWeek3BeSocial} > ol`); const destination = document.querySelector(`${outlineIds.exampleWeek3BeSocial} > ol`);
triggerKeyListener(current, destination, 'ArrowDown'); // @TODO: Get these from the UI Toolkit triggerKeyListener(current, destination, keys.down);
expect(destination.focus).toHaveBeenCalled(); expect(destination.focus).toHaveBeenCalled();
}); });
...@@ -58,7 +58,7 @@ describe('Course outline factory', () => { ...@@ -58,7 +58,7 @@ describe('Course outline factory', () => {
const current = document.querySelector(outlineIds.homeworkEssays); const current = document.querySelector(outlineIds.homeworkEssays);
const destination = document.querySelector(outlineIds.exampleWeek3BeSocial); const destination = document.querySelector(outlineIds.exampleWeek3BeSocial);
triggerKeyListener(current, destination, 'ArrowDown'); // @TODO: Get these from the UI Toolkit triggerKeyListener(current, destination, keys.down);
expect(destination.focus).toHaveBeenCalled(); expect(destination.focus).toHaveBeenCalled();
}); });
...@@ -69,7 +69,7 @@ describe('Course outline factory', () => { ...@@ -69,7 +69,7 @@ describe('Course outline factory', () => {
const current = document.querySelector(outlineIds.homeworkEssays); const current = document.querySelector(outlineIds.homeworkEssays);
const destination = document.querySelector(outlineIds.homeworkLabsAndDemos); const destination = document.querySelector(outlineIds.homeworkLabsAndDemos);
triggerKeyListener(current, destination, 'ArrowUp'); // @TODO: Get these from the UI Toolkit triggerKeyListener(current, destination, keys.up);
expect(destination.focus).toHaveBeenCalled(); expect(destination.focus).toHaveBeenCalled();
}); });
...@@ -78,7 +78,7 @@ describe('Course outline factory', () => { ...@@ -78,7 +78,7 @@ describe('Course outline factory', () => {
const current = document.querySelector(outlineIds.lesson3BeSocial); const current = document.querySelector(outlineIds.lesson3BeSocial);
const destination = document.querySelector(`${outlineIds.exampleWeek3BeSocial} > ol`); const destination = document.querySelector(`${outlineIds.exampleWeek3BeSocial} > ol`);
triggerKeyListener(current, destination, 'ArrowUp'); // @TODO: Get these from the UI Toolkit triggerKeyListener(current, destination, keys.up);
expect(destination.focus).toHaveBeenCalled(); expect(destination.focus).toHaveBeenCalled();
}); });
...@@ -87,7 +87,7 @@ describe('Course outline factory', () => { ...@@ -87,7 +87,7 @@ describe('Course outline factory', () => {
const current = document.querySelector(outlineIds.exampleWeek3BeSocial); const current = document.querySelector(outlineIds.exampleWeek3BeSocial);
const destination = document.querySelector(outlineIds.homeworkEssays); const destination = document.querySelector(outlineIds.homeworkEssays);
triggerKeyListener(current, destination, 'ArrowUp'); // @TODO: Get these from the UI Toolkit triggerKeyListener(current, destination, keys.up);
expect(destination.focus).toHaveBeenCalled(); expect(destination.focus).toHaveBeenCalled();
}); });
......
...@@ -161,5 +161,5 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -161,5 +161,5 @@ from openedx.core.djangolib.markup import HTML, Text
</%static:require_module_async> </%static:require_module_async>
<%static:webpack entry="CourseOutline"> <%static:webpack entry="CourseOutline">
new CourseOutline(); new CourseOutline('.block-tree');
</%static:webpack> </%static:webpack>
...@@ -2,11 +2,14 @@ ...@@ -2,11 +2,14 @@
"name": "edx", "name": "edx",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"babel-core": "^6.23.0",
"babel-loader": "^6.4.0",
"babel-preset-env": "^1.2.1",
"backbone": "~1.3.2", "backbone": "~1.3.2",
"backbone.paginator": "~2.0.3", "backbone.paginator": "~2.0.3",
"coffee-script": "1.6.1", "coffee-script": "1.6.1",
"edx-pattern-library": "0.18.1", "edx-pattern-library": "0.18.1",
"edx-ui-toolkit": "1.5.1", "edx-ui-toolkit": "1.5.2",
"hls.js": "0.7.2", "hls.js": "0.7.2",
"jquery": "~2.2.0", "jquery": "~2.2.0",
"jquery-migrate": "^1.4.1", "jquery-migrate": "^1.4.1",
...@@ -17,12 +20,11 @@ ...@@ -17,12 +20,11 @@
"requirejs": "~2.3.2", "requirejs": "~2.3.2",
"uglify-js": "2.7.0", "uglify-js": "2.7.0",
"underscore": "~1.8.3", "underscore": "~1.8.3",
"underscore.string": "~3.3.4" "underscore.string": "~3.3.4",
"webpack": "^2.2.1",
"webpack-bundle-tracker": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.23.0",
"babel-loader": "^6.4.0",
"babel-preset-env": "^1.2.1",
"edx-custom-a11y-rules": "0.1.3", "edx-custom-a11y-rules": "0.1.3",
"eslint-config-edx": "^2.0.1", "eslint-config-edx": "^2.0.1",
"eslint-config-edx-es5": "^2.0.0", "eslint-config-edx-es5": "^2.0.0",
...@@ -43,8 +45,6 @@ ...@@ -43,8 +45,6 @@
"pa11y-reporter-json-oldnode": "1.0.0", "pa11y-reporter-json-oldnode": "1.0.0",
"plato": "1.2.2", "plato": "1.2.2",
"sinon": "^1.17.7", "sinon": "^1.17.7",
"squirejs": "^0.1.0", "squirejs": "^0.1.0"
"webpack": "^2.2.1",
"webpack-bundle-tracker": "^0.2.0"
} }
} }
...@@ -704,14 +704,17 @@ def execute_compile_sass(args): ...@@ -704,14 +704,17 @@ def execute_compile_sass(args):
) )
@task def execute_webpack(prod):
@no_help sh(cmd("NODE_ENV={node_env} STATIC_ROOT={static_root} $(npm bin)/webpack".format(
def execute_webpack(): node_env="production" if prod else "development",
sh(cmd("$(npm bin)/webpack")) static_root=Env.get_django_setting("STATIC_ROOT", "lms")
)))
def execute_webpack_watch(): def execute_webpack_watch():
run_background_process("$(npm bin)/webpack --watch --watch-poll=200") run_background_process("STATIC_ROOT={static_root} $(npm bin)/webpack --watch --watch-poll=200".format(
static_root=Env.get_django_setting("STATIC_ROOT", "lms")
))
def get_parsed_option(command_opts, opt_key, default=None): def get_parsed_option(command_opts, opt_key, default=None):
...@@ -845,7 +848,7 @@ def update_assets(args): ...@@ -845,7 +848,7 @@ def update_assets(args):
process_xmodule_assets() process_xmodule_assets()
process_npm_assets() process_npm_assets()
compile_coffeescript() compile_coffeescript()
execute_webpack() execute_webpack(prod=(args.settings != "devstack"))
# Compile sass for themes and system # Compile sass for themes and system
execute_compile_sass(args) execute_compile_sass(args)
......
...@@ -4,6 +4,7 @@ import ddt ...@@ -4,6 +4,7 @@ import ddt
from paver.easy import call_task from paver.easy import call_task
from .utils import PaverTestCase from .utils import PaverTestCase
from ..utils.envs import Env
EXPECTED_COFFEE_COMMAND = ( EXPECTED_COFFEE_COMMAND = (
u"node_modules/.bin/coffee --compile `find {platform_root}/lms " u"node_modules/.bin/coffee --compile `find {platform_root}/lms "
...@@ -40,8 +41,11 @@ EXPECTED_RUN_SERVER_COMMAND = ( ...@@ -40,8 +41,11 @@ EXPECTED_RUN_SERVER_COMMAND = (
EXPECTED_INDEX_COURSE_COMMAND = ( EXPECTED_INDEX_COURSE_COMMAND = (
u"python manage.py {system} --settings={settings} reindex_course --setup" u"python manage.py {system} --settings={settings} reindex_course --setup"
) )
EXPECTED_PRINT_SETTINGS_COMMAND = (
u"python manage.py {system} --settings=aws print_settings STATIC_ROOT --format=value 2>/dev/null"
)
EXPECTED_WEBPACK_COMMAND = ( EXPECTED_WEBPACK_COMMAND = (
u"$(npm bin)/webpack" u"NODE_ENV={node_env} STATIC_ROOT={static_root} $(npm bin)/webpack"
) )
...@@ -236,7 +240,11 @@ class TestPaverServerTasks(PaverTestCase): ...@@ -236,7 +240,11 @@ class TestPaverServerTasks(PaverTestCase):
expected_messages.append(u"xmodule_assets common/static/xmodule") expected_messages.append(u"xmodule_assets common/static/xmodule")
expected_messages.append(u"install npm_assets") expected_messages.append(u"install npm_assets")
expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=self.platform_root)) expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=self.platform_root))
expected_messages.append(EXPECTED_WEBPACK_COMMAND) expected_messages.append(EXPECTED_PRINT_SETTINGS_COMMAND.format(system="lms"))
expected_messages.append(EXPECTED_WEBPACK_COMMAND.format(
node_env="production" if expected_asset_settings != "devstack" else "development",
static_root=None
))
expected_messages.extend(self.expected_sass_commands(system=system, asset_settings=expected_asset_settings)) expected_messages.extend(self.expected_sass_commands(system=system, asset_settings=expected_asset_settings))
if expected_collect_static: if expected_collect_static:
expected_messages.append(EXPECTED_COLLECT_STATIC_COMMAND.format( expected_messages.append(EXPECTED_COLLECT_STATIC_COMMAND.format(
...@@ -274,7 +282,11 @@ class TestPaverServerTasks(PaverTestCase): ...@@ -274,7 +282,11 @@ class TestPaverServerTasks(PaverTestCase):
expected_messages.append(u"xmodule_assets common/static/xmodule") expected_messages.append(u"xmodule_assets common/static/xmodule")
expected_messages.append(u"install npm_assets") expected_messages.append(u"install npm_assets")
expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=self.platform_root)) expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=self.platform_root))
expected_messages.append(EXPECTED_WEBPACK_COMMAND) expected_messages.append(EXPECTED_PRINT_SETTINGS_COMMAND.format(system="lms"))
expected_messages.append(EXPECTED_WEBPACK_COMMAND.format(
node_env="production" if expected_asset_settings != "devstack" else "development",
static_root=None
))
expected_messages.extend(self.expected_sass_commands(asset_settings=expected_asset_settings)) expected_messages.extend(self.expected_sass_commands(asset_settings=expected_asset_settings))
if expected_collect_static: if expected_collect_static:
expected_messages.append(EXPECTED_COLLECT_STATIC_COMMAND.format( expected_messages.append(EXPECTED_COLLECT_STATIC_COMMAND.format(
......
...@@ -7,6 +7,8 @@ import sys ...@@ -7,6 +7,8 @@ import sys
import json import json
from lazy import lazy from lazy import lazy
from path import Path as path from path import Path as path
from pavelib.utils.cmd import django_cmd
from paver.easy import sh
import memcache import memcache
...@@ -171,6 +173,23 @@ class Env(object): ...@@ -171,6 +173,23 @@ class Env(object):
else: else:
SERVICE_VARIANT = 'lms' SERVICE_VARIANT = 'lms'
@classmethod
def get_django_setting(self, django_setting, system):
"""
Interrogate Django environment for specific settings values
"""
value = sh(
django_cmd(
system,
os.environ.get("EDX_PLATFORM_SETTINGS", "aws"),
"print_settings {django_setting} --format=value 2>/dev/null".format(
django_setting=django_setting
)
),
capture=True
)
return unicode(value).strip()
@lazy @lazy
def env_tokens(self): def env_tokens(self):
""" """
......
...@@ -64,6 +64,9 @@ echo "npm version is `npm --version`" ...@@ -64,6 +64,9 @@ echo "npm version is `npm --version`"
echo "--> Cleaning npm cache" echo "--> Cleaning npm cache"
npm cache clean npm cache clean
echo "setting variables that paver will use to get Django Settings"
export EDX_PLATFORM_SETTINGS=test_static_optimized
# Log any paver or ansible command timing # Log any paver or ansible command timing
TIMESTAMP=$(date +%s) TIMESTAMP=$(date +%s)
export PAVER_TIMER_LOG="test_root/log/timing.paver.$TIMESTAMP.log" export PAVER_TIMER_LOG="test_root/log/timing.paver.$TIMESTAMP.log"
......
/* eslint-env node */
'use strict';
var path = require('path'); var path = require('path');
var webpack = require('webpack'); var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker'); var BundleTracker = require('webpack-bundle-tracker');
...@@ -13,7 +17,7 @@ var wpconfig = { ...@@ -13,7 +17,7 @@ var wpconfig = {
output: { output: {
path: path.resolve(__dirname, 'common/static/bundles'), path: path.resolve(__dirname, 'common/static/bundles'),
filename: '[name]-[hash].js', filename: isProd ? '[name].[chunkhash].js' : '[name].js',
libraryTarget: 'window' libraryTarget: 'window'
}, },
...@@ -29,7 +33,8 @@ var wpconfig = { ...@@ -29,7 +33,8 @@ var wpconfig = {
debug: !isProd debug: !isProd
}), }),
new BundleTracker({ new BundleTracker({
filename: './webpack-stats.json' path: process.env.STATIC_ROOT,
filename: 'webpack-stats.json'
}) })
], ],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment