Webpack 4

This commit is contained in:
SleepWalker 2019-07-01 06:39:59 +03:00
parent 4783f1bad0
commit fa46046f5f
28 changed files with 2149 additions and 1808 deletions

View File

@ -3,6 +3,7 @@
"description": "",
"main": "src/index.js",
"author": "SleepWalker <mybox@udf.su>",
"private": true,
"maintainers": [
{
"name": "ErickSkrauch",
@ -18,6 +19,10 @@
"engines": {
"node": ">=8.0.0"
},
"workspaces": [
"webpack-utils",
"scripts"
],
"scripts": {
"start": "yarn run clean && yarn run build:dll && NODE_PATH=./src webpack-dev-server --progress --colors",
"clean": "rm -rf dist/",
@ -26,8 +31,8 @@
"lint": "eslint ./src",
"flow": "flow",
"i18n:collect": "babel-node ./scripts/i18n-collect.js",
"i18n:push": "babel-node --presets flow --plugins transform-es2015-modules-commonjs ./scripts/i18n-crowdin.js push",
"i18n:pull": "babel-node --presets flow --plugins transform-es2015-modules-commonjs ./scripts/i18n-crowdin.js pull",
"i18n:push": "babel-node ./scripts/i18n-crowdin.js push",
"i18n:pull": "babel-node ./scripts/i18n-crowdin.js pull",
"build": "yarn run clean && yarn run build:webpack --progress",
"build:install": "yarn install && check-node-version",
"build:webpack": "NODE_PATH=./src webpack --colors -p --bail",
@ -88,24 +93,23 @@
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.0",
"babel-plugin-react-intl": "^2.0.0",
"bundle-loader": "^0.5.4",
"check-node-version": "^2.1.0",
"core-js": "3",
"csp-webpack-plugin": "^1.0.2",
"css-loader": "^0.28.0",
"csp-webpack-plugin": "^2.0.2",
"css-loader": "^3.0.0",
"cssnano": "^4.1.10",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.7.1",
"eslint": "^4.0.0",
"eslint-plugin-flowtype": "^2.46.3",
"eslint-plugin-react": "^7.3.0",
"exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.11.0",
"exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^4.0.0",
"flow-bin": "~0.102.0",
"fontgen-loader": "^0.2.1",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.0.0",
"imports-loader": "^0.7.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"imports-loader": "^0.8.0",
"jsdom": "^15.1.1",
"json-loader": "^0.5.4",
"karma": "^4.1.0",
@ -114,26 +118,24 @@
"karma-nyan-reporter": "^0.2.3",
"karma-sinon": "^1.0.4",
"karma-sourcemap-loader": "*",
"karma-webpack": "^2.0.0",
"karma-webpack": "^4.0.2",
"loader-utils": "^1.0.0",
"mocha": "^6.1.4",
"node-sass": "^4.7.2",
"postcss-import": "^9.0.0",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-scss": "^0.4.0",
"postcss-url": "SleepWalker/postcss-url#switch-to-async-api",
"raw-loader": "^0.5.1",
"postcss-scss": "^2.0.0",
"raw-loader": "^3.0.0",
"react-test-renderer": "^16.7.0",
"sass-loader": "^4.0.0",
"scripts": "file:./scripts",
"sass-loader": "^7.1.0",
"sinon": "^7.3.2",
"sitemap-webpack-plugin": "^0.5.1",
"style-loader": "^0.18.0",
"sitemap-webpack-plugin": "^0.6.0",
"style-loader": "^0.23.1",
"unexpected": "^11.6.1",
"unexpected-sinon": "^10.5.1",
"url-loader": "^0.5.7",
"webpack": "^2.0.0",
"webpack-dev-server": "^2.0.0",
"webpack-utils": "file:./webpack-utils"
"url-loader": "^2.0.1",
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
}
}

View File

@ -1,3 +1,69 @@
module.exports = {
plugins: {}
};
/* eslint-env node */
const path = require('path');
const loaderUtils = require('loader-utils');
const fileCache = {};
const isProduction = process.argv.some((arg) => arg === '-p');
const rootPath = path.resolve('./src');
module.exports = ({ webpack: loader }) => ({
syntax: 'postcss-scss',
plugins: {
'postcss-import': {
addModulesDirectories: ['./src'],
resolve: ((defaultResolve) => (url, basedir, importOptions) =>
defaultResolve(
// mainly to remove '~' from request
loaderUtils.urlToRequest(url),
basedir,
importOptions
))(require('postcss-import/lib/resolve-id')),
load: ((defaultLoad) => (filename, importOptions) => {
if (/\.font.(js|json)$/.test(filename)) {
// separately process calls to font loader
// e.g. `@import '~icons.font.json';`
if (!fileCache[filename] || !isProduction) {
// do not execute loader on the same file twice
// this is an overcome for a bug with ExtractTextPlugin, for isProduction === true
// when @imported files may be processed multiple times
fileCache[filename] = new Promise((resolve, reject) =>
loader.loadModule(filename, (err, source) => {
if (err) {
reject(err);
return;
}
resolve(loader.exec(source, rootPath));
})
);
}
return fileCache[filename];
}
return defaultLoad(filename, importOptions);
})(require('postcss-import/lib/load-content'))
},
// TODO: for some reason cssnano strips out @mixin declarations
// cssnano: {
// /**
// * TODO: cssnano options
// */
// // autoprefixer: {
// // add: true,
// // remove: true,
// // browsers: ['last 2 versions']
// // },
// // safe: true,
// // // отключаем минификацию цветов, что бы она не ломала такие выражения:
// // // composes: black from '~./buttons.scss';
// // colormin: false,
// // discardComments: {
// // removeAll: true
// // }
// preset: 'default'
// }
}
});

View File

@ -5,6 +5,7 @@ import {sync as globSync} from 'glob';
import {sync as mkdirpSync} from 'mkdirp';
import chalk from 'chalk';
import prompt from 'prompt';
import localesMap from './../src/i18n/index.json';
const MESSAGES_PATTERN = `${__dirname}/../dist/messages/**/*.json`;

View File

@ -10,11 +10,13 @@
"author": "",
"license": "ISC",
"dependencies": {
"@babel/node": "^7.0.0",
"chalk": "^1.1.3",
"crowdin-api": "erickskrauch/crowdin-api#add_missed_methods_and_fix_files_uploading",
"glob": "^7.1.4",
"iso-639-1": "^2.0.3",
"mkdirp": "^0.5.1",
"multi-progress": "^2.0.0",
"node-babel": "^0.1.2",
"prompt": "^1.0.0"
}
}

View File

@ -151,7 +151,7 @@ $lightBorderColor: #eee;
}
.accountIcon {
composes: minecraft-character from 'components/ui/icons.scss';
composes: minecraft-character from '~components/ui/icons.scss';
float: left;
@ -185,7 +185,7 @@ $lightBorderColor: #eee;
}
.addIcon {
composes: plus from 'components/ui/icons.scss';
composes: plus from '~components/ui/icons.scss';
color: $green;
position: relative;
@ -194,7 +194,7 @@ $lightBorderColor: #eee;
}
.nextIcon {
composes: arrowRight from 'components/ui/icons.scss';
composes: arrowRight from '~components/ui/icons.scss';
position: relative;
float: right;
@ -213,7 +213,7 @@ $lightBorderColor: #eee;
}
.logoutIcon {
composes: exit from 'components/ui/icons.scss';
composes: exit from '~components/ui/icons.scss';
color: #cdcdcd;
float: right;

View File

@ -5,7 +5,7 @@
}
.descriptionImage {
composes: envelope from 'components/ui/icons.scss';
composes: envelope from '~components/ui/icons.scss';
font-size: 100px;
color: $blue;

View File

@ -20,12 +20,12 @@
}
.successBackground {
composes: checkmark from 'components/ui/icons.scss';
composes: checkmark from '~components/ui/icons.scss';
@extend .iconBackground;
}
.failBackground {
composes: close from 'components/ui/icons.scss';
composes: close from '~components/ui/icons.scss';
@extend .iconBackground;
}

View File

@ -8,11 +8,11 @@
}
.login {
composes: email from 'components/auth/password/password.scss';
composes: email from '~components/auth/password/password.scss';
}
.editLogin {
composes: pencil from 'components/ui/icons.scss';
composes: pencil from '~components/ui/icons.scss';
position: relative;
bottom: 1px;

View File

@ -5,7 +5,7 @@
/* Form state */
.contactForm {
composes: popupWrapper from 'components/ui/popup/popup.scss';
composes: popupWrapper from '~components/ui/popup/popup.scss';
@include popupBounding(500px);
}
@ -46,13 +46,13 @@
/* Success State */
.successState {
composes: popupWrapper from 'components/ui/popup/popup.scss';
composes: popupWrapper from '~components/ui/popup/popup.scss';
@include popupBounding(320px);
}
.successBody {
composes: body from 'components/ui/popup/popup.scss';
composes: body from '~components/ui/popup/popup.scss';
text-align: center;
}
@ -64,7 +64,7 @@
}
.successIcon {
composes: checkmark from 'components/ui/icons.scss';
composes: checkmark from '~components/ui/icons.scss';
font-size: 90px;
color: #AAA;

View File

@ -162,7 +162,7 @@
}
.appItemToggleIcon {
composes: arrowRight from 'components/ui/icons.scss';
composes: arrowRight from '~components/ui/icons.scss';
position: relative;
left: 0;
@ -208,7 +208,7 @@ $appDetailsContainerRightLeftPadding: 30px;
}
.pencilIcon {
composes: pencil from 'components/ui/icons.scss';
composes: pencil from '~components/ui/icons.scss';
font-size: 14px;
position: relative;
@ -254,7 +254,7 @@ $appDetailsContainerRightLeftPadding: 30px;
}
.continueActionLink {
composes: textLink from 'index.scss';
composes: textLink from '~index.scss';
font-family: $font-family-title;
font-size: 14px;

View File

@ -14,7 +14,7 @@
}
.langTriggerIcon {
composes: globe from 'components/ui/icons.scss';
composes: globe from '~components/ui/icons.scss';
position: relative;
bottom: 1px;

View File

@ -9,13 +9,13 @@
}
.languageSwitcher {
composes: popupWrapper from 'components/ui/popup/popup.scss';
composes: popupWrapper from '~components/ui/popup/popup.scss';
@include popupBounding(400px);
}
.languageSwitcherBody {
composes: body from 'components/ui/popup/popup.scss';
composes: body from '~components/ui/popup/popup.scss';
display: flex;
flex-direction: column;
@ -32,7 +32,7 @@
}
.searchIcon {
composes: search from 'components/ui/icons.scss';
composes: search from '~components/ui/icons.scss';
position: absolute;
top: 14px;
@ -126,7 +126,7 @@ $languageListBorderStyle: 1px solid $languageListBorderColor;
// Реализация радио кнопки. Когда у нас будет нормальный компонент радио кнопок, нужно будет перейти на него
.languageCircle {
composes: checkmark from 'components/ui/icons.scss';
composes: checkmark from '~components/ui/icons.scss';
position: relative;
box-sizing: border-box;
@ -209,7 +209,7 @@ $languageListBorderStyle: 1px solid $languageListBorderColor;
}
.improveTranslatesIcon {
composes: translate from 'components/ui/icons.scss';
composes: translate from '~components/ui/icons.scss';
color: lighter($blue);
font-size: 22px;

View File

@ -1,7 +1,7 @@
@import '~components/ui/popup/popup.scss';
.requestPasswordForm {
composes: popupWrapper from 'components/ui/popup/popup.scss';
composes: popupWrapper from '~components/ui/popup/popup.scss';
@include popupBounding(280px);
}
@ -11,7 +11,7 @@
}
.lockIcon {
composes: lock from 'components/ui/icons.scss';
composes: lock from '~components/ui/icons.scss';
font-size: 90px;
color: #AAA;

View File

@ -80,7 +80,7 @@ $formColumnWidth: 416px;
}
.paramEditIcon {
composes: pencil from 'components/ui/icons.scss';
composes: pencil from '~components/ui/icons.scss';
color: $white;
transition: .4s;

View File

@ -27,7 +27,7 @@
}
.backIcon {
composes: arrowLeft from 'components/ui/icons.scss';
composes: arrowLeft from '~components/ui/icons.scss';
position: relative;
}

View File

@ -55,7 +55,7 @@ $dropdownPadding: 15px;
}
.toggleIcon {
composes: selecter from 'components/ui/icons.scss';
composes: selecter from '~components/ui/icons.scss';
position: absolute;
right: $dropdownPadding;

View File

@ -284,7 +284,7 @@
.mark {
composes: markPosition;
composes: checkmark from 'components/ui/icons.scss';
composes: checkmark from '~components/ui/icons.scss';
border: 2px #dcd8cd solid;

View File

@ -23,7 +23,7 @@ $headerHeight: 60px;
}
.headerControl {
composes: black from 'components/ui/buttons.scss';
composes: black from '~components/ui/buttons.scss';
float: left;
overflow: hidden;
@ -119,7 +119,7 @@ $bodyTopBottomPadding: 15px;
}
.close {
composes: close from 'components/ui/icons.scss';
composes: close from '~components/ui/icons.scss';
position: absolute;
right: 5px;

View File

@ -13,7 +13,7 @@
}
.activeAccountButton {
composes: green from 'components/ui/buttons.scss';
composes: green from '~components/ui/buttons.scss';
}
.activeAccountExpanded {
@ -31,7 +31,7 @@
}
.userIcon {
composes: user from 'components/ui/icons.scss';
composes: user from '~components/ui/icons.scss';
position: relative;
bottom: 2px;
@ -40,7 +40,7 @@
}
.expandIcon {
composes: caret from 'components/ui/icons.scss';
composes: caret from '~components/ui/icons.scss';
margin-left: 4px;
font-size: 6px;

View File

@ -52,7 +52,7 @@
}
.checkmark {
composes: checkmark from 'components/ui/icons.scss';
composes: checkmark from '~components/ui/icons.scss';
color: lighter($green);
font-size: 66px;

View File

@ -34,17 +34,17 @@ export default {
messages: { [string]: string };
}> {
const promises: Array<Promise<any>> = [
import(/* webpackChunkName: "[request]-locale" */`react-intl/locale-data/${locale}.js`),
import(/* webpackChunkName: "[request]-locale" */`i18n/${locale}.json`),
import(/* webpackChunkName: "locale-[request]" */`react-intl/locale-data/${locale}.js`),
import(/* webpackChunkName: "locale-[request]" */`i18n/${locale}.json`),
];
if (needPolyfill) {
promises.push(import(/* webpackChunkName: "[request]-intl-polyfill" */ 'intl'));
promises.push(import(/* webpackChunkName: "[request]-intl-polyfill" */`intl/locale-data/jsonp/${locale}.js`));
promises.push(import(/* webpackChunkName: "intl-polyfill" */'intl'));
promises.push(import(/* webpackChunkName: "intl-polyfill-[request]" */`intl/locale-data/jsonp/${locale}.js`));
}
return Promise.all(promises)
.then(([localeData, messages]) => {
.then(([localeData, {default: messages}]) => {
addLocaleData(localeData);
return {locale, messages};

View File

@ -1,36 +0,0 @@
// при использовании sass-loader теряется контекст в импортированных модулях
// из-за чего css-loader не может правильно обработать относительные url
//
// препроцессим урлы перед тем, как пропускать их через sass-loader
// урлы, начинающиеся с / будут оставлены как есть
const cssUrl = require('postcss-url');
const loaderUtils = require('loader-utils');
// /#.+$/ - strip #hash part of svg font url
const urlToRequest = (url) => loaderUtils.urlToRequest(url.replace(/\??#.+$/, ''), true);
const urlPostfix = (url) => {
var idx = url.indexOf('?#');
if (idx < 0) {
idx = url.indexOf('#');
}
return idx >= 0 ? url.slice(idx) : '';
};
module.exports = function(loader) {
return cssUrl({
url: (url, decl, from, dirname, to, options, result) =>
new Promise((resolve, reject) =>
loaderUtils.isUrlRequest(url) ? loader.loadModule(urlToRequest(url), (err, source) =>
err ? reject(err) : resolve(
loader.exec(`
var __webpack_public_path__ = '${loader.options.output.publicPath}';
${source}
`, dirname) + urlPostfix(url)
)
) : resolve(url)
)
});
};

View File

@ -0,0 +1,201 @@
/* eslint-env node */
/**
* Forked from
*
* https://github.com/DragonsInn/fontgen-loader
*/
const loaderUtils = require('loader-utils');
const fontgen = require('webfonts-generator');
const path = require('path');
const glob = require('glob');
const mimeTypes = {
eot: 'application/vnd.ms-fontobject',
svg: 'image/svg+xml',
ttf: 'application/x-font-ttf',
woff: 'application/font-woff'
};
function absolute(from, to) {
if (arguments.length < 2) {
return function(to) {
return path.resolve(from, to);
};
}
return path.resolve(from, to);
}
function getFilesAndDeps(patterns, context) {
let files = [];
const filesDeps = [];
let directoryDeps = [];
function addFile(file) {
filesDeps.push(file);
files.push(absolute(context, file));
}
function addByGlob(globExp) {
const globOptions = { cwd: context };
const foundFiles = glob.sync(globExp, globOptions);
files = files.concat(foundFiles.map(absolute(context)));
const globDirs = glob.sync(`${path.dirname(globExp)}/`, globOptions);
directoryDeps = directoryDeps.concat(globDirs.map(absolute(context)));
}
// Re-work the files array.
patterns.forEach((pattern) => {
if (glob.hasMagic(pattern)) {
addByGlob(pattern);
} else {
addFile(pattern);
}
});
return {
files,
dependencies: {
directories: directoryDeps,
files: filesDeps
}
};
}
module.exports = function(content) {
this.cacheable();
const params = loaderUtils.getOptions(this) || {};
let config;
try {
config = JSON.parse(content);
} catch (ex) {
config = this.exec(content, this.resourcePath);
}
config.__dirname = path.dirname(this.resourcePath);
// Sanity check
/*
if(typeof config.fontName != "string" || typeof config.files != "array") {
this.reportError("Typemismatch in your config. Verify your config for correct types.");
return false;
}
*/
const filesAndDeps = getFilesAndDeps(config.files, this.context);
filesAndDeps.dependencies.files.forEach(this.addDependency.bind(this));
filesAndDeps.dependencies.directories.forEach(
this.addContextDependency.bind(this)
);
config.files = filesAndDeps.files;
// With everything set up, let's make an ACTUAL config.
let formats = config.types || ['eot', 'woff', 'ttf', 'svg'];
if (formats.constructor !== Array) {
formats = [formats];
}
const fontconf = {
files: config.files,
fontName: config.fontName,
types: formats,
order: formats,
fontHeight: config.fontHeight || 1000, // Fixes conversion issues with small svgs
templateOptions: {
baseClass: config.baseClass || 'icon',
classPrefix: 'classPrefix' in config ? config.classPrefix : 'icon-'
},
dest: '',
writeFiles: false,
formatOptions: config.formatOptions || {}
};
// This originally was in the object notation itself.
// Unfortunately that actually broke my editor's syntax-highlighting...
// ... what a shame.
if (typeof config.rename == 'function') {
fontconf.rename = config.rename;
} else {
fontconf.rename = function(filePath) {
return path.basename(filePath, '.svg');
};
}
if (config.cssTemplate) {
fontconf.cssTemplate = absolute(this.context, config.cssTemplate);
}
for (const option in config.templateOptions) {
if (config.templateOptions.hasOwnProperty(option)) {
fontconf.templateOptions[option] = config.templateOptions[option];
}
}
// svgicons2svgfont stuff
const keys = [
'fixedWidth',
'centerHorizontally',
'normalize',
'fontHeight',
'round',
'descent'
];
for (const x in keys) {
if (typeof config[keys[x]] != 'undefined') {
fontconf[keys[x]] = config[keys[x]];
}
}
const cb = this.async();
const opts = this._compiler.options;
const pub = opts.output.publicPath || '/';
const embed = !!params.embed;
if (fontconf.cssTemplate) {
this.addDependency(fontconf.cssTemplate);
}
fontgen(fontconf, (err, res) => {
if (err) {
return cb(err);
}
const urls = {};
for (const i in formats) {
const format = formats[i];
if (embed) {
urls[format] = `data:${
mimeTypes[format]
};charset=utf-8;base64,${new Buffer(res[format]).toString(
'base64'
)}`;
} else {
let filename
= config.fileName
|| params.fileName
|| '[hash]-[fontname][ext]';
filename = filename
.replace('[fontname]', fontconf.fontName)
.replace('[ext]', `.${format}`);
const url = loaderUtils.interpolateName(
this.context,
filename,
{
content: res[format]
}
);
urls[format] = path.join(pub, url).replace(/\\/g, '/');
this.emitFile(url, res[format]);
}
}
const code = res.generateCss(urls);
cb(null, `module.exports = ${JSON.stringify(code)}`);
});
};

View File

@ -3,19 +3,28 @@ module.exports = function(content) {
content = JSON.parse(content);
const moduleId = this.context
.replace(this.options.resolve.root, '')
.replace(this.rootContext, '')
// TODO: can't find the way to strip out this path part programmatically
// this is a directory from resolve.modules config
// may be this may work: .replace(this._compiler.options.resolve.root, '')
.replace('src/', '')
.replace(/^\/|\/$/g, '')
.replace(/\//g, '.');
content = JSON.stringify(Object.keys(content).reduce(function(translations, key) {
translations[key] = {
id: moduleId + '.' + key,
content = JSON.stringify(
Object.keys(content).reduce(
(translations, key) => ({
...translations,
[key]: {
id: `${moduleId}.${key}`,
defaultMessage: content[key]
};
}
}),
{}
)
);
return translations;
}, {}));
return `import { defineMessages } from 'react-intl';
return 'import { defineMessages } from \'react-intl\';'
+ 'export default defineMessages(' + content + ')';
export default defineMessages(${content})`;
};

View File

@ -5,6 +5,8 @@
"keywords": [],
"author": "",
"dependencies": {
"loader-utils": "^1.0.0"
"loader-utils": "^1.0.0",
"webfonts-generator": "^0.3.2",
"glob": "^6.0.2"
}
}

View File

@ -3,29 +3,26 @@
require('@babel/register');
const path = require('path');
const webpack = require('webpack');
const loaderUtils = require('loader-utils');
const chalk = require('chalk');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const cssUrl = require('webpack-utils/cssUrl');
const cssImport = require('postcss-import');
const SitemapPlugin = require('sitemap-webpack-plugin').default;
const CSPPlugin = require('csp-webpack-plugin');
const localeFlags = require('./src/components/i18n/localeFlags').default;
const SUPPORTED_LANGUAGES = Object.keys(require('./src/i18n/index.json'));
const localeFlags = require('./src/components/i18n/localeFlags').default;
const rootPath = path.resolve('./src');
const outputPath = path.join(__dirname, 'dist');
const packageJson = require('./package.json');
let config = {};
try {
config = require('./config/env.js');
} catch (err) {
console.log(chalk.yellow('\nCan not find config/env.js. Running with defaults\n\n'));
console.log(
chalk.yellow('\nCan not find config/env.js. Running with defaults\n\n')
);
if (err.code !== 'MODULE_NOT_FOUND') {
console.error(err);
@ -55,38 +52,11 @@ const isSilent = isCI || process.argv.some((arg) => /quiet/.test(arg));
const isCspEnabled = false;
process.env.NODE_ENV = isProduction ? 'production' : 'development';
if (isTest) {
process.env.NODE_ENV = 'test';
}
const CSS_CLASS_TEMPLATE = isProduction ? '[hash:base64:5]' : '[path][name]-[local]';
const fileCache = {};
const cssLoaderQuery = {
modules: true,
importLoaders: 2,
url: false,
localIdentName: CSS_CLASS_TEMPLATE,
/**
* cssnano options
*/
sourcemap: !isProduction,
autoprefixer: {
add: true,
remove: true,
browsers: ['last 2 versions']
},
safe: true,
// отключаем минификацию цветов, что бы она не ломала такие выражения:
// composes: black from './buttons.scss';
colormin: false,
discardComments: {
removeAll: true
}
};
const webpackConfig = {
cache: true,
@ -105,24 +75,33 @@ const webpackConfig = {
extensions: ['.js', '.jsx', '.json']
},
externals: isTest ? {
externals: isTest
? {
sinon: 'sinon',
// http://airbnb.io/enzyme/docs/guides/webpack.html
cheerio: 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true,
'react/addons': true
} : {},
}
: {},
devtool: 'cheap-module-source-map',
plugins: [
new webpack.DefinePlugin({
'window.SENTRY_CDN': config.sentryCdn ? JSON.stringify(config.sentryCdn) : undefined,
'window.GA_ID': config.ga && config.ga.id ? JSON.stringify(config.ga.id) : undefined,
'window.SENTRY_CDN': config.sentryCdn
? JSON.stringify(config.sentryCdn)
: undefined,
'window.GA_ID':
config.ga && config.ga.id
? JSON.stringify(config.ga.id)
: undefined,
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
APP_ENV: JSON.stringify(config.environment || process.env.NODE_ENV),
APP_ENV: JSON.stringify(
config.environment || process.env.NODE_ENV
),
__VERSION__: JSON.stringify(config.version || ''),
__DEV__: !isProduction,
__TEST__: isTest,
@ -139,31 +118,37 @@ const webpackConfig = {
minify: {
collapseWhitespace: isProduction
},
isCspEnabled,
isCspEnabled
}),
new SitemapPlugin('https://account.ely.by', [
new SitemapPlugin(
'https://account.ely.by',
[
'/',
'/register',
'/resend-activation',
'/activation',
'/forgot-password',
'/rules',
], {
'/rules'
],
{
lastMod: true,
changeFreq: 'weekly',
}),
changeFreq: 'weekly'
}
),
new webpack.ProvidePlugin({
React: 'react'
}),
// restrict webpack import context, to create chunks only for supported locales
// @see services/i18n.js
new webpack.ContextReplacementPlugin(
/locale-data/, new RegExp(`/(${SUPPORTED_LANGUAGES.join('|')})\\.js`)
/locale-data/,
new RegExp(`/(${SUPPORTED_LANGUAGES.join('|')})\\.js`)
),
// @see components/i18n/localeFlags.js
new webpack.ContextReplacementPlugin(
/flag-icon-css\/flags\/4x3/, new RegExp(`/(${localeFlags.getCountryList().join('|')})\\.svg`)
),
/flag-icon-css\/flags\/4x3/,
new RegExp(`/(${localeFlags.getCountryList().join('|')})\\.svg`)
)
],
module: {
@ -174,15 +159,28 @@ const webpackConfig = {
'style-loader',
{
loader: 'css-loader',
options: cssLoaderQuery
options: {
modules: {
localIdentName: isProduction
? '[hash:base64:5]'
: '[path][name]-[local]'
},
importLoaders: 2,
sourceMap: !isProduction
}
},
{
loader: 'sass-loader',
options: {
sourceMap: !isProduction
}
},
'sass-loader',
{
loader: 'postcss-loader',
options: {
syntax: 'postcss-scss',
ident: 'postcss',
plugins: postcss
sourceMap: !isProduction,
config: { path: __dirname }
}
}
]
@ -213,12 +211,6 @@ const webpackConfig = {
query: {
name: 'assets/fonts/[name].[ext]?[hash]'
}
},
{
test: /\.json$/,
exclude: /(intl|font)\.json/,
loader: 'json-loader'
},
{
test: /\.html$/,
@ -226,20 +218,25 @@ const webpackConfig = {
},
{
test: /\.intl\.json$/,
loader: 'babel-loader!intl'
type: 'javascript/auto',
use: ['babel-loader', 'intl-loader']
},
{
// this is consumed by postcss-import
// @see postcss.config.js
test: /\.font\.(js|json)$/,
loader: 'raw-loader!fontgen-loader'
type: 'javascript/auto',
use: ['fontgen-loader']
}
]
},
resolveLoader: {
alias: {
intl: path.resolve('webpack-utils/intl-loader')
'intl-loader': path.resolve('./webpack-utils/intl-loader'),
'fontgen-loader': path.resolve('./webpack-utils/fontgen-loader')
}
}
},
};
if (isProduction) {
@ -248,7 +245,7 @@ if (isProduction) {
// remove style-loader from chain and pass through ExtractTextPlugin
loader.use = ExtractTextPlugin.extract({
fallbackLoader: loader.use[0], // style-loader
loader: loader.use,
loader: loader.use
});
}
});
@ -264,12 +261,11 @@ if (isProduction) {
webpackConfig.devtool = 'hidden-source-map';
const ignoredPlugins = [
'flag-icon-css',
];
const ignoredPlugins = ['flag-icon-css'];
webpackConfig.entry.vendor = Object.keys(packageJson.dependencies)
.filter((module) => !ignoredPlugins.includes(module));
webpackConfig.entry.vendor = Object.keys(packageJson.dependencies).filter(
(module) => !ignoredPlugins.includes(module)
);
} else {
webpackConfig.plugins.push(
new webpack.DllReferencePlugin({
@ -280,15 +276,12 @@ if (isProduction) {
}
if (!isProduction && !isTest) {
webpackConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(),
);
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
if (config.apiHost) {
webpackConfig.devServer = {
host: 'localhost',
port: 8080,
proxy: {
proxy: config.apiHost && {
'/api': {
target: config.apiHost,
changeOrigin: true, // add host http-header, based on target
@ -300,10 +293,10 @@ if (!isProduction && !isTest) {
historyApiFallback: true
};
}
}
if (isCspEnabled) {
webpackConfig.plugins.push(new CSPPlugin({
webpackConfig.plugins.push(
new CSPPlugin({
'default-src': '\'none\'',
'style-src': ['\'self\'', '\'unsafe-inline\''],
'script-src': [
@ -313,17 +306,21 @@ if (isCspEnabled) {
'https://www.google-analytics.com',
'https://recaptcha.net/recaptcha/',
'https://www.gstatic.com/recaptcha/',
'https://www.gstatic.cn/recaptcha/',
'https://www.gstatic.cn/recaptcha/'
],
'img-src': ['\'self\'', 'data:', 'www.google-analytics.com'],
'font-src': ['\'self\'', 'data:'],
'connect-src': ['\'self\'', 'https://sentry.ely.by'].concat(isProduction ? [] : ['ws://localhost:8080']),
'connect-src': ['\'self\'', 'https://sentry.ely.by'].concat(
isProduction ? [] : ['ws://localhost:8080']
),
'frame-src': [
'https://www.google.com/recaptcha/',
'https://recaptcha.net/recaptcha/',
'https://recaptcha.net/recaptcha/'
],
'report-uri': 'https://sentry.ely.by/api/2/csp-report/?sentry_key=088e7718236a4f91937a81fb319a93f6',
}));
'report-uri':
'https://sentry.ely.by/api/2/csp-report/?sentry_key=088e7718236a4f91937a81fb319a93f6'
})
);
}
if (isDockerized) {
@ -351,41 +348,4 @@ if (isSilent) {
};
}
function postcss() {
return [
cssImport({
path: rootPath,
resolve: ((defaultResolve) =>
(url, basedir, importOptions) =>
defaultResolve(loaderUtils.urlToRequest(url), basedir, importOptions)
)(require('postcss-import/lib/resolve-id')),
load: ((defaultLoad) =>
(filename, importOptions) => {
if (/\.font.(js|json)$/.test(filename)) {
if (!fileCache[filename] || !isProduction) {
// do not execute loader on the same file twice
// this is an overcome for a bug with ExtractTextPlugin, for isProduction === true
// when @imported files may be processed mutiple times
fileCache[filename] = new Promise((resolve, reject) =>
this.loadModule(filename, (err, source) =>
err ? reject(err) : resolve(this.exec(source, rootPath))
)
);
}
return fileCache[filename];
}
return defaultLoad(filename, importOptions);
}
)(require('postcss-import/lib/load-content'))
}),
cssUrl(this)
];
}
module.exports = webpackConfig;

View File

@ -1,23 +1,26 @@
/* eslint-env node */
const path = require('path');
const webpack = require('webpack');
const vendor = Object.keys(require('./package.json').dependencies);
const isProduction = process.argv.some((arg) => arg === '-p');
const supportedLocales = require('./src/i18n/index.json');
const isTest = process.argv.some((arg) => arg.indexOf('karma') !== -1);
process.env.NODE_ENV = 'development';
if (isTest) {
process.env.NODE_ENV = 'test';
}
if (isProduction) {
throw new Error('Dll plugin should be used for dev mode only');
}
const outputPath = path.join(__dirname, 'dll');
const webpackConfig = {
mode: 'development',
entry: {
vendor: vendor.concat([
'core-js/library',
@ -26,12 +29,10 @@ const webpackConfig = {
'redux-devtools-log-monitor',
'react-transform-hmr',
'react-transform-catch-errors',
'redbox-react',
'react-intl/locale-data/en.js',
'react-intl/locale-data/ru.js',
'react-intl/locale-data/be.js',
'react-intl/locale-data/uk.js'
])
]).concat(
Object.keys(supportedLocales)
.map((locale) => `react-intl/locale-data/${locale}.js`)
)
},
output: {
@ -54,11 +55,7 @@ const webpackConfig = {
name: '[name]',
path: path.join(outputPath, '[name].json')
})
].concat(isProduction ? [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(true),
new webpack.optimize.UglifyJsPlugin()
] : [])
]
};
module.exports = webpackConfig;

3175
yarn.lock

File diff suppressed because it is too large Load Diff