From fbffdc5fc713f26d84ad00bacb5279abb9529bfd Mon Sep 17 00:00:00 2001 From: Ryan Randall Date: Tue, 26 Sep 2017 14:16:35 -0400 Subject: [PATCH] Webpack dev server (#902) * Initial webpack config. Moving to ES6 modules. Some module cleanup. * WIP * WIP * Removing commented out code. * Removing old deps. * Removing the build script (used for testing). * Working tests. * Switching to node api over cli. * Updating per comments. Still need to fix server.js to properly run the selenium tests. * Cleaning up the config. * More cleanup. * Bringing back server.js for selenium tests. * Bringing back old dependencies. * Adding back eslint rules for webpack config. Final cleanup for webpack config. * Pointing to correct pre-existing module versions. Adding some extra logic to server.js to ensure it gets transpiled properly. * Getting make build to work again. Updating package.json with some shortcut scripts. * Resolving conflict. * Reverting back to commonjs modules. * Removing extra spaces in babelrc --- build/.gitkeep | 0 contrib/auto-render/auto-render.js | 5 +- contrib/auto-render/splitAtDelimiters.js | 2 +- contrib/copy-tex/copy-tex.js | 2 +- contrib/copy-tex/katex2tex.js | 2 +- katex.js | 6 ++- package.json | 14 +++++- server.js | 9 +++- src/Options.js | 2 +- src/ParseError.js | 2 +- src/Parser.js | 2 +- src/Settings.js | 2 +- src/Style.js | 2 +- src/buildCommon.js | 2 +- src/buildHTML.js | 4 +- src/buildMathML.js | 10 ++-- src/buildTree.js | 8 ++-- src/delimiter.js | 14 +++--- src/domTree.js | 2 +- src/environments.js | 2 +- src/environments/array.js | 16 +++---- src/fontMetrics.js | 2 +- src/fontMetricsData.js | 2 +- src/functions/delimsizing.js | 6 +-- src/mathMLTree.js | 2 +- src/parseTree.js | 2 +- src/stretchy.js | 10 ++-- src/svgGeometry.js | 2 +- src/utils.js | 2 +- test/unicode-spec.js | 6 +-- webpack.config.js | 59 ++++++++++++++++++++++++ webpackDevServer.js | 12 +++++ 32 files changed, 154 insertions(+), 59 deletions(-) delete mode 100644 build/.gitkeep create mode 100644 webpack.config.js create mode 100644 webpackDevServer.js diff --git a/build/.gitkeep b/build/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/contrib/auto-render/auto-render.js b/contrib/auto-render/auto-render.js index 89bcb109..c06bd7b8 100644 --- a/contrib/auto-render/auto-render.js +++ b/contrib/auto-render/auto-render.js @@ -1,7 +1,7 @@ /* eslint no-console:0 */ /* global katex */ -const splitAtDelimiters = require("./splitAtDelimiters"); +import splitAtDelimiters from "./splitAtDelimiters"; const splitWithDelimiters = function(text, delimiters) { let data = [{type: "text", data: text}]; @@ -99,4 +99,5 @@ const renderMathInElement = function(elem, options) { renderElem(elem, optionsCopy); }; -module.exports = renderMathInElement; + +export default renderMathInElement; diff --git a/contrib/auto-render/splitAtDelimiters.js b/contrib/auto-render/splitAtDelimiters.js index 8c9cde3a..ef8f3eed 100644 --- a/contrib/auto-render/splitAtDelimiters.js +++ b/contrib/auto-render/splitAtDelimiters.js @@ -99,4 +99,4 @@ const splitAtDelimiters = function(startData, leftDelim, rightDelim, display) { return finalData; }; -module.exports = splitAtDelimiters; +export default splitAtDelimiters; diff --git a/contrib/copy-tex/copy-tex.js b/contrib/copy-tex/copy-tex.js index 7426f438..ca6e08ac 100644 --- a/contrib/copy-tex/copy-tex.js +++ b/contrib/copy-tex/copy-tex.js @@ -1,4 +1,4 @@ -const katexReplaceWithTex = require('./katex2tex'); +import katexReplaceWithTex from './katex2tex'; // Global copy handler to modify behavior on .katex elements. document.addEventListener('copy', function(event) { diff --git a/contrib/copy-tex/katex2tex.js b/contrib/copy-tex/katex2tex.js index 1109c401..9b64c786 100644 --- a/contrib/copy-tex/katex2tex.js +++ b/contrib/copy-tex/katex2tex.js @@ -49,4 +49,4 @@ export const katexReplaceWithTex = function(fragment, return fragment; }; -module.exports = katexReplaceWithTex; +export default katexReplaceWithTex; diff --git a/katex.js b/katex.js index 8327592f..65caa921 100644 --- a/katex.js +++ b/katex.js @@ -43,6 +43,7 @@ if (typeof document !== "undefined") { } } + /** * Parse and build an expression, and return the markup for that. */ @@ -61,7 +62,8 @@ const generateParseTree = function(expression, options) { return parseTree(expression, settings); }; -module.exports = { + +const katex = { render: render, renderToString: renderToString, /** @@ -72,3 +74,5 @@ module.exports = { __parse: generateParseTree, ParseError: ParseError, }; + +export default katex; diff --git a/package.json b/package.json index 6cb20ec0..0f8ea3c9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "devDependencies": { "babel-eslint": "^7.2.0", "babel-jest": "^20.0.3", + "babel-loader": "^7.1.2", + "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-class-properties": "^6.23.0", "babel-plugin-transform-runtime": "^6.15.0", "babel-preset-es2015": "^6.18.0", @@ -37,6 +39,7 @@ "js-yaml": "^3.3.1", "jspngopt": "^0.2.0", "less": "~2.7.1", + "less-plugin-clean-css": "^1.5.1", "morgan": "^1.7.0", "nomnom": "^1.8.1", "object-assign": "^4.1.0", @@ -44,7 +47,9 @@ "pre-commit": "^1.2.2", "selenium-webdriver": "^2.48.2", "sri-toolbox": "^0.2.0", - "uglify-js": "~2.7.5" + "uglify-js": "~2.7.5", + "webpack": "^3.6.0", + "webpack-dev-server": "^2.8.2" }, "bin": "cli.js", "scripts": { @@ -52,8 +57,13 @@ "flow": "flow", "jest": "jest", "coverage": "jest --coverage", + "copy": "cp -a static/. build/ && cp -a contrib build/", + "clean": "rm -rf build/* node_modules/", + "clean-install": "npm run clean && npm i", "test": "check-dependencies && npm run lint && npm run flow && npm run jest", - "start": "check-dependencies && node server.js", + "build-css": "lessc --clean-css static/katex.less build/katex.css", + "prestart": "npm run build-css && npm run copy", + "start": "check-dependencies && node webpackDevServer.js", "prepublishOnly": "make NIS= dist" }, "pre-commit": [ diff --git a/server.js b/server.js index d87ceb50..2e2619ec 100644 --- a/server.js +++ b/server.js @@ -27,7 +27,14 @@ function serveBrowserified(file, standaloneName) { } const options = { - transform: [babelify], + transform: [babelify.configure({ + presets: ["es2015", "flow"], + plugins: [ + "transform-runtime", + "transform-class-properties", + "add-module-exports", + ], + })], }; if (standaloneName) { options.standalone = standaloneName; diff --git a/src/Options.js b/src/Options.js index 36b05a41..58a91bad 100644 --- a/src/Options.js +++ b/src/Options.js @@ -304,4 +304,4 @@ class Options { } } -module.exports = Options; +export default Options; diff --git a/src/ParseError.js b/src/ParseError.js index 56ed1d85..0b33b817 100644 --- a/src/ParseError.js +++ b/src/ParseError.js @@ -72,4 +72,4 @@ class ParseError { // $FlowFixMe More hackery ParseError.prototype.__proto__ = Error.prototype; -module.exports = ParseError; +export default ParseError; diff --git a/src/Parser.js b/src/Parser.js index 9541e20b..d1b93312 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -928,7 +928,7 @@ export default class Parser { // Lexer's tokenRegex is constructed to always have matching // first/last characters. if (arg.length < 2 || arg.charAt(0) !== arg.slice(-1)) { - throw new ParseError(`\\verb assertion failed -- + throw new ParseError(`\\verb assertion failed -- please report what input caused this bug`); } arg = arg.slice(1, -1); // remove first and last char diff --git a/src/Settings.js b/src/Settings.js index 19f8fca9..7291e140 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -45,4 +45,4 @@ class Settings { } } -module.exports = Settings; +export default Settings; diff --git a/src/Style.js b/src/Style.js index bcaf899f..6304c472 100644 --- a/src/Style.js +++ b/src/Style.js @@ -122,7 +122,7 @@ const cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc]; const text = [D, Dc, T, Tc, T, Tc, T, Tc]; // We only export some of the styles. -module.exports = { +export default { DISPLAY: styles[D], TEXT: styles[T], SCRIPT: styles[S], diff --git a/src/buildCommon.js b/src/buildCommon.js index e6f7293e..99ebddd2 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -505,7 +505,7 @@ const fontMap = { }, }; -module.exports = { +export default { fontMap: fontMap, makeSymbol: makeSymbol, mathsym: mathsym, diff --git a/src/buildHTML.js b/src/buildHTML.js index c136fdc3..7f184e55 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -10,13 +10,15 @@ import ParseError from "./ParseError"; import Style from "./Style"; -import buildCommon, { makeSpan } from "./buildCommon"; +import buildCommon from "./buildCommon"; import delimiter from "./delimiter"; import domTree from "./domTree"; import { calculateSize } from "./units"; import utils from "./utils"; import stretchy from "./stretchy"; +const makeSpan = buildCommon.makeSpan; + const isSpace = function(node) { return node instanceof domTree.span && node.classes[0] === "mspace"; }; diff --git a/src/buildMathML.js b/src/buildMathML.js index c8bab014..f35de0d5 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -6,7 +6,7 @@ * parser. */ -import buildCommon, { makeSpan, fontMap } from "./buildCommon"; +import buildCommon from "./buildCommon"; import fontMetrics from "./fontMetrics"; import mathMLTree from "./mathMLTree"; import ParseError from "./ParseError"; @@ -50,9 +50,9 @@ const getVariant = function(group, options) { value = symbols[mode][value].replace; } - const fontName = fontMap[font].fontName; + const fontName = buildCommon.fontMap[font].fontName; if (fontMetrics.getCharacterMetrics(value, fontName)) { - return fontMap[options.font].variant; + return buildCommon.fontMap[options.font].variant; } return null; @@ -458,7 +458,7 @@ groupTypes.sizing = function(group, options) { groupTypes.verb = function(group, options) { const text = new mathMLTree.TextNode(buildCommon.makeVerb(group, options)); const node = new mathMLTree.MathNode("mtext", [text]); - node.setAttribute("mathvariant", fontMap["mathtt"].variant); + node.setAttribute("mathvariant", buildCommon.fontMap["mathtt"].variant); return node; }; @@ -690,5 +690,5 @@ export default function buildMathML(tree, texExpression, options) { const math = new mathMLTree.MathNode("math", [semantics]); // You can't style nodes, so we wrap the node in a span. - return makeSpan(["katex-mathml"], [math]); + return buildCommon.makeSpan(["katex-mathml"], [math]); } diff --git a/src/buildTree.js b/src/buildTree.js index 773d326e..6059fa7e 100644 --- a/src/buildTree.js +++ b/src/buildTree.js @@ -1,6 +1,6 @@ import buildHTML from "./buildHTML"; import buildMathML from "./buildMathML"; -import { makeSpan } from "./buildCommon"; +import buildCommon from "./buildCommon"; import Options from "./Options"; import Settings from "./Settings"; import Style from "./Style"; @@ -24,15 +24,15 @@ const buildTree = function(tree, expression, settings) { const mathMLNode = buildMathML(tree, expression, options); const htmlNode = buildHTML(tree, options); - const katexNode = makeSpan(["katex"], [ + const katexNode = buildCommon.makeSpan(["katex"], [ mathMLNode, htmlNode, ]); if (settings.displayMode) { - return makeSpan(["katex-display"], [katexNode]); + return buildCommon.makeSpan(["katex-display"], [katexNode]); } else { return katexNode; } }; -module.exports = buildTree; +export default buildTree; diff --git a/src/delimiter.js b/src/delimiter.js index 98ee924f..a94b6111 100644 --- a/src/delimiter.js +++ b/src/delimiter.js @@ -24,7 +24,7 @@ import ParseError from "./ParseError"; import Style from "./Style"; import domTree from "./domTree"; -import buildCommon, { makeSpan } from "./buildCommon"; +import buildCommon from "./buildCommon"; import fontMetrics from "./fontMetrics"; import symbols from "./symbols"; import utils from "./utils"; @@ -50,7 +50,7 @@ const getMetrics = function(symbol, font) { const styleWrap = function(delim, toStyle, options, classes) { const newOptions = options.havingBaseStyle(toStyle); - const span = makeSpan( + const span = buildCommon.makeSpan( (classes || []).concat(newOptions.sizingClasses(options)), [delim], options); @@ -103,7 +103,7 @@ const mathrmSize = function(value, size, mode, options) { const makeLargeDelim = function(delim, size, center, options, mode, classes) { const inner = mathrmSize(delim, size, mode, options); const span = styleWrap( - makeSpan(["delimsizing", "size" + size], [inner], options), + buildCommon.makeSpan(["delimsizing", "size" + size], [inner], options), Style.TEXT, options, classes); if (center) { centerSpan(span, options, Style.TEXT); @@ -124,9 +124,9 @@ const makeInner = function(symbol, font, mode) { sizeClass = "delim-size4"; } - const inner = makeSpan( + const inner = buildCommon.makeSpan( ["delimsizinginner", sizeClass], - [makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]); + [buildCommon.makeSpan([], [buildCommon.makeSymbol(symbol, font, mode)])]); // Since this will be passed into `makeVList` in the end, wrap the element // in the appropriate tag that VList uses. @@ -310,7 +310,7 @@ const makeStackedDelim = function(delim, heightTotal, center, options, mode, const inner = buildCommon.makeVList(inners, "bottom", depth, newOptions); return styleWrap( - makeSpan(["delimsizing", "mult"], [inner], newOptions), + buildCommon.makeSpan(["delimsizing", "mult"], [inner], newOptions), Style.TEXT, options, classes); }; @@ -606,7 +606,7 @@ const makeLeftRightDelim = function(delim, height, depth, options, mode, classes); }; -module.exports = { +export default { sizedDelim: makeSizedDelim, customSizedDelim: makeCustomSizedDelim, leftRightDelim: makeLeftRightDelim, diff --git a/src/domTree.js b/src/domTree.js index 2100d954..259f26ed 100644 --- a/src/domTree.js +++ b/src/domTree.js @@ -443,7 +443,7 @@ class lineNode { } } -module.exports = { +export default { span: span, documentFragment: documentFragment, symbolNode: symbolNode, diff --git a/src/environments.js b/src/environments.js index 9cdc5f53..a6324b52 100644 --- a/src/environments.js +++ b/src/environments.js @@ -10,7 +10,7 @@ const environments = { }, }; -export {environments as default}; +export default environments; // All environment definitions should be imported below import "./environments/array.js"; diff --git a/src/environments/array.js b/src/environments/array.js index a35acd13..3746341e 100644 --- a/src/environments/array.js +++ b/src/environments/array.js @@ -1,5 +1,5 @@ // @flow -import buildCommon, {makeSpan} from "../buildCommon"; +import buildCommon from "../buildCommon"; import defineEnvironment from "../defineEnvironment"; import mathMLTree from "../mathMLTree"; import ParseError from "../ParseError"; @@ -191,14 +191,14 @@ const htmlBuilder = function(group, options) { // If there is more than one separator in a row, add a space // between them. if (!firstSeparator) { - colSep = makeSpan(["arraycolsep"], []); + colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep.style.width = options.fontMetrics().doubleRuleSep + "em"; cols.push(colSep); } if (colDescr.separator === "|") { - const separator = makeSpan( + const separator = buildCommon.makeSpan( ["vertical-separator"], []); separator.style.height = totalHeight + "em"; @@ -224,7 +224,7 @@ const htmlBuilder = function(group, options) { if (c > 0 || group.value.hskipBeforeAndAfter) { sepwidth = utils.deflt(colDescr.pregap, arraycolsep); if (sepwidth !== 0) { - colSep = makeSpan(["arraycolsep"], []); + colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep.style.width = sepwidth + "em"; cols.push(colSep); } @@ -244,7 +244,7 @@ const htmlBuilder = function(group, options) { } col = buildCommon.makeVList(col, "individualShift", null, options); - col = makeSpan( + col = buildCommon.makeSpan( ["col-align-" + (colDescr.align || "c")], [col]); cols.push(col); @@ -252,14 +252,14 @@ const htmlBuilder = function(group, options) { if (c < nc - 1 || group.value.hskipBeforeAndAfter) { sepwidth = utils.deflt(colDescr.postgap, arraycolsep); if (sepwidth !== 0) { - colSep = makeSpan(["arraycolsep"], []); + colSep = buildCommon.makeSpan(["arraycolsep"], []); colSep.style.width = sepwidth + "em"; cols.push(colSep); } } } - body = makeSpan(["mtable"], cols); - return makeSpan(["mord"], [body], options); + body = buildCommon.makeSpan(["mtable"], cols); + return buildCommon.makeSpan(["mord"], [body], options); }; const mathmlBuilder = function(group, options) { diff --git a/src/fontMetrics.js b/src/fontMetrics.js index 3251568a..ecc77545 100644 --- a/src/fontMetrics.js +++ b/src/fontMetrics.js @@ -297,7 +297,7 @@ const getFontMetrics = function(size: number): FontMetrics { return fontMetricsBySizeIndex[sizeIndex]; }; -module.exports = { +export default { getFontMetrics: getFontMetrics, getCharacterMetrics: getCharacterMetrics, }; diff --git a/src/fontMetricsData.js b/src/fontMetricsData.js index 39d4f5c9..4e28fd32 100644 --- a/src/fontMetricsData.js +++ b/src/fontMetricsData.js @@ -1755,4 +1755,4 @@ const fontMetricsData = { }, }; -module.exports = fontMetricsData; +export default fontMetricsData; diff --git a/src/functions/delimsizing.js b/src/functions/delimsizing.js index 408cc5fe..9e5a8b30 100644 --- a/src/functions/delimsizing.js +++ b/src/functions/delimsizing.js @@ -1,5 +1,5 @@ // @flow -import buildCommon, {makeSpan} from "../buildCommon"; +import buildCommon from "../buildCommon"; import defineFunction from "../defineFunction"; import delimiter from "../delimiter"; import mathMLTree from "../mathMLTree"; @@ -85,7 +85,7 @@ defineFunction({ if (delim === ".") { // Empty delimiters still count as elements, even though they don't // show anything. - return makeSpan([group.value.mclass]); + return buildCommon.makeSpan([group.value.mclass]); } // Use delimiter.sizedDelim to generate the delimiter. @@ -203,7 +203,7 @@ defineFunction({ // Add it to the end of the expression. inner.push(rightDelim); - return makeSpan(["minner"], inner, options); + return buildCommon.makeSpan(["minner"], inner, options); }, mathmlBuilder: (group, options) => { const inner = mml.buildExpression(group.value.body, options); diff --git a/src/mathMLTree.js b/src/mathMLTree.js index 49650be1..b3416963 100644 --- a/src/mathMLTree.js +++ b/src/mathMLTree.js @@ -100,7 +100,7 @@ class TextNode { } } -module.exports = { +export default { MathNode: MathNode, TextNode: TextNode, }; diff --git a/src/parseTree.js b/src/parseTree.js index b0a95357..0850fb92 100644 --- a/src/parseTree.js +++ b/src/parseTree.js @@ -17,4 +17,4 @@ const parseTree = function(toParse, settings) { return parser.parse(); }; -module.exports = parseTree; +export default parseTree; diff --git a/src/stretchy.js b/src/stretchy.js index 0a71b10b..bea4c3ab 100644 --- a/src/stretchy.js +++ b/src/stretchy.js @@ -4,10 +4,10 @@ * and other CSS trickery. */ -const domTree = require("./domTree"); -const buildCommon = require("./buildCommon"); -const mathMLTree = require("./mathMLTree"); -const utils = require("./utils"); +import domTree from "./domTree"; +import buildCommon from "./buildCommon"; +import mathMLTree from "./mathMLTree"; +import utils from "./utils"; const stretchyCodePoint = { widehat: "^", @@ -311,7 +311,7 @@ const encloseSpan = function(inner, label, pad, options) { return img; }; -module.exports = { +export default { encloseSpan: encloseSpan, mathMLnode: mathMLnode, svgSpan: svgSpan, diff --git a/src/svgGeometry.js b/src/svgGeometry.js index b2783c07..f228755d 100644 --- a/src/svgGeometry.js +++ b/src/svgGeometry.js @@ -261,6 +261,6 @@ c-1 5-5 9-11 9h-2L532 67 19 159h-2c-5 0-9-4-11-9l-5-22c-1-6 2-12 8-13z`, -11 10h-1L1182 67 15 340h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`, }; -module.exports = { +export default { path: path, }; diff --git a/src/utils.js b/src/utils.js index 12a0dc7d..7cd26f94 100644 --- a/src/utils.js +++ b/src/utils.js @@ -94,7 +94,7 @@ function clearNode(node) { setTextContent(node, ""); } -module.exports = { +export default { contains: contains, deflt: deflt, escape: escape, diff --git a/test/unicode-spec.js b/test/unicode-spec.js index 6037ec84..62a4d97f 100644 --- a/test/unicode-spec.js +++ b/test/unicode-spec.js @@ -3,9 +3,9 @@ /* global expect: false */ /* global it: false */ /* global describe: false */ -const ParseError = require("../src/ParseError"); -const parseTree = require("../src/parseTree"); -const Settings = require("../src/Settings"); +import ParseError from "../src/ParseError"; +import parseTree from "../src/parseTree"; +import Settings from "../src/Settings"; const defaultSettings = new Settings({}); diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..e6126a92 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,59 @@ +const path = require('path'); + +const katexConfig = { + entry: ['./katex.js'], + output: { + path: path.join(__dirname, 'build'), + filename: 'katex.js', + library: 'katex', + libraryTarget: 'umd', + libraryExport: 'default', + }, +}; + +const copyTexConfig = { + entry: ['./contrib/copy-tex/copy-tex.js'], + output: { + path: path.join(__dirname, 'build', 'contrib', 'copy-tex'), + filename: 'copy-tex.js', + }, +}; + +const autoRenderConfig = { + entry: ['./contrib/auto-render/auto-render.js'], + output: { + path: path.join(__dirname, 'build', 'contrib', 'auto-render'), + filename: 'auto-render.js', + library: 'renderMathInElement', + libraryTarget: 'umd', + libraryExport: 'default', + }, +}; + +const commonConfig = { + module: { + rules: [ + { + test: /\.js$/, + use: 'babel-loader', + exclude: /node_modules\//, + }, + ], + }, + devtool: 'eval-source-map', +}; + +module.exports = { + compilerConfig: [ + Object.assign({}, katexConfig, commonConfig), + Object.assign({}, copyTexConfig, commonConfig), + Object.assign({}, autoRenderConfig, commonConfig), + ], + devServerConfig: { + publicPath: '/', + contentBase: path.join(__dirname, 'build'), + stats: { + colors: true, + }, + }, +}; diff --git a/webpackDevServer.js b/webpackDevServer.js new file mode 100644 index 00000000..df41d3e8 --- /dev/null +++ b/webpackDevServer.js @@ -0,0 +1,12 @@ +const WebpackDevServer = require("webpack-dev-server"); +const webpack = require("webpack"); +const webpackConfig = require("./webpack.config.js"); +const PORT = 7936; + +webpackConfig.compilerConfig.forEach((config) => { + config.entry.unshift(`webpack-dev-server/client?http://localhost:${PORT}/`); +}); +const compiler = webpack(webpackConfig.compilerConfig); +const server = new WebpackDevServer(compiler, webpackConfig.devServerConfig); + +server.listen(PORT);