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

View File

@ -1,3 +1,69 @@
module.exports = { /* eslint-env node */
plugins: {} 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 {sync as mkdirpSync} from 'mkdirp';
import chalk from 'chalk'; import chalk from 'chalk';
import prompt from 'prompt'; import prompt from 'prompt';
import localesMap from './../src/i18n/index.json'; import localesMap from './../src/i18n/index.json';
const MESSAGES_PATTERN = `${__dirname}/../dist/messages/**/*.json`; const MESSAGES_PATTERN = `${__dirname}/../dist/messages/**/*.json`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,17 +34,17 @@ export default {
messages: { [string]: string }; messages: { [string]: string };
}> { }> {
const promises: Array<Promise<any>> = [ const promises: Array<Promise<any>> = [
import(/* webpackChunkName: "[request]-locale" */`react-intl/locale-data/${locale}.js`), import(/* webpackChunkName: "locale-[request]" */`react-intl/locale-data/${locale}.js`),
import(/* webpackChunkName: "[request]-locale" */`i18n/${locale}.json`), import(/* webpackChunkName: "locale-[request]" */`i18n/${locale}.json`),
]; ];
if (needPolyfill) { if (needPolyfill) {
promises.push(import(/* webpackChunkName: "[request]-intl-polyfill" */ 'intl')); promises.push(import(/* webpackChunkName: "intl-polyfill" */'intl'));
promises.push(import(/* webpackChunkName: "[request]-intl-polyfill" */`intl/locale-data/jsonp/${locale}.js`)); promises.push(import(/* webpackChunkName: "intl-polyfill-[request]" */`intl/locale-data/jsonp/${locale}.js`));
} }
return Promise.all(promises) return Promise.all(promises)
.then(([localeData, messages]) => { .then(([localeData, {default: messages}]) => {
addLocaleData(localeData); addLocaleData(localeData);
return {locale, messages}; 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); content = JSON.parse(content);
const moduleId = this.context 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, '')
.replace(/\//g, '.'); .replace(/\//g, '.');
content = JSON.stringify(Object.keys(content).reduce(function(translations, key) { content = JSON.stringify(
translations[key] = { Object.keys(content).reduce(
id: moduleId + '.' + key, (translations, key) => ({
defaultMessage: content[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": [], "keywords": [],
"author": "", "author": "",
"dependencies": { "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'); require('@babel/register');
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const loaderUtils = require('loader-utils');
const chalk = require('chalk'); const chalk = require('chalk');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-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 SitemapPlugin = require('sitemap-webpack-plugin').default;
const CSPPlugin = require('csp-webpack-plugin'); 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 SUPPORTED_LANGUAGES = Object.keys(require('./src/i18n/index.json'));
const localeFlags = require('./src/components/i18n/localeFlags').default;
const rootPath = path.resolve('./src'); const rootPath = path.resolve('./src');
const outputPath = path.join(__dirname, 'dist'); const outputPath = path.join(__dirname, 'dist');
const packageJson = require('./package.json'); const packageJson = require('./package.json');
let config = {}; let config = {};
try { try {
config = require('./config/env.js'); config = require('./config/env.js');
} catch (err) { } 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') { if (err.code !== 'MODULE_NOT_FOUND') {
console.error(err); console.error(err);
@ -55,38 +52,11 @@ const isSilent = isCI || process.argv.some((arg) => /quiet/.test(arg));
const isCspEnabled = false; const isCspEnabled = false;
process.env.NODE_ENV = isProduction ? 'production' : 'development'; process.env.NODE_ENV = isProduction ? 'production' : 'development';
if (isTest) { if (isTest) {
process.env.NODE_ENV = 'test'; 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 = { const webpackConfig = {
cache: true, cache: true,
@ -105,24 +75,33 @@ const webpackConfig = {
extensions: ['.js', '.jsx', '.json'] extensions: ['.js', '.jsx', '.json']
}, },
externals: isTest ? { externals: isTest
sinon: 'sinon', ? {
// http://airbnb.io/enzyme/docs/guides/webpack.html sinon: 'sinon',
cheerio: 'window', // http://airbnb.io/enzyme/docs/guides/webpack.html
'react/lib/ExecutionEnvironment': true, cheerio: 'window',
'react/lib/ReactContext': true, 'react/lib/ExecutionEnvironment': true,
'react/addons': true 'react/lib/ReactContext': true,
} : {}, 'react/addons': true
}
: {},
devtool: 'cheap-module-source-map', devtool: 'cheap-module-source-map',
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'window.SENTRY_CDN': config.sentryCdn ? JSON.stringify(config.sentryCdn) : undefined, 'window.SENTRY_CDN': config.sentryCdn
'window.GA_ID': config.ga && config.ga.id ? JSON.stringify(config.ga.id) : undefined, ? JSON.stringify(config.sentryCdn)
: undefined,
'window.GA_ID':
config.ga && config.ga.id
? JSON.stringify(config.ga.id)
: undefined,
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_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 || ''), __VERSION__: JSON.stringify(config.version || ''),
__DEV__: !isProduction, __DEV__: !isProduction,
__TEST__: isTest, __TEST__: isTest,
@ -139,31 +118,37 @@ const webpackConfig = {
minify: { minify: {
collapseWhitespace: isProduction collapseWhitespace: isProduction
}, },
isCspEnabled, isCspEnabled
}),
new SitemapPlugin('https://account.ely.by', [
'/',
'/register',
'/resend-activation',
'/activation',
'/forgot-password',
'/rules',
], {
lastMod: true,
changeFreq: 'weekly',
}), }),
new SitemapPlugin(
'https://account.ely.by',
[
'/',
'/register',
'/resend-activation',
'/activation',
'/forgot-password',
'/rules'
],
{
lastMod: true,
changeFreq: 'weekly'
}
),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
React: 'react' React: 'react'
}), }),
// restrict webpack import context, to create chunks only for supported locales // restrict webpack import context, to create chunks only for supported locales
// @see services/i18n.js // @see services/i18n.js
new webpack.ContextReplacementPlugin( new webpack.ContextReplacementPlugin(
/locale-data/, new RegExp(`/(${SUPPORTED_LANGUAGES.join('|')})\\.js`) /locale-data/,
new RegExp(`/(${SUPPORTED_LANGUAGES.join('|')})\\.js`)
), ),
// @see components/i18n/localeFlags.js // @see components/i18n/localeFlags.js
new webpack.ContextReplacementPlugin( 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: { module: {
@ -174,15 +159,28 @@ const webpackConfig = {
'style-loader', 'style-loader',
{ {
loader: 'css-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', loader: 'postcss-loader',
options: { options: {
syntax: 'postcss-scss',
ident: 'postcss', ident: 'postcss',
plugins: postcss sourceMap: !isProduction,
config: { path: __dirname }
} }
} }
] ]
@ -213,12 +211,6 @@ const webpackConfig = {
query: { query: {
name: 'assets/fonts/[name].[ext]?[hash]' name: 'assets/fonts/[name].[ext]?[hash]'
} }
},
{
test: /\.json$/,
exclude: /(intl|font)\.json/,
loader: 'json-loader'
}, },
{ {
test: /\.html$/, test: /\.html$/,
@ -226,20 +218,25 @@ const webpackConfig = {
}, },
{ {
test: /\.intl\.json$/, 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)$/, test: /\.font\.(js|json)$/,
loader: 'raw-loader!fontgen-loader' type: 'javascript/auto',
use: ['fontgen-loader']
} }
] ]
}, },
resolveLoader: { resolveLoader: {
alias: { 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) { if (isProduction) {
@ -248,7 +245,7 @@ if (isProduction) {
// remove style-loader from chain and pass through ExtractTextPlugin // remove style-loader from chain and pass through ExtractTextPlugin
loader.use = ExtractTextPlugin.extract({ loader.use = ExtractTextPlugin.extract({
fallbackLoader: loader.use[0], // style-loader fallbackLoader: loader.use[0], // style-loader
loader: loader.use, loader: loader.use
}); });
} }
}); });
@ -264,12 +261,11 @@ if (isProduction) {
webpackConfig.devtool = 'hidden-source-map'; webpackConfig.devtool = 'hidden-source-map';
const ignoredPlugins = [ const ignoredPlugins = ['flag-icon-css'];
'flag-icon-css',
];
webpackConfig.entry.vendor = Object.keys(packageJson.dependencies) webpackConfig.entry.vendor = Object.keys(packageJson.dependencies).filter(
.filter((module) => !ignoredPlugins.includes(module)); (module) => !ignoredPlugins.includes(module)
);
} else { } else {
webpackConfig.plugins.push( webpackConfig.plugins.push(
new webpack.DllReferencePlugin({ new webpack.DllReferencePlugin({
@ -280,50 +276,51 @@ if (isProduction) {
} }
if (!isProduction && !isTest) { if (!isProduction && !isTest) {
webpackConfig.plugins.push( webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
new webpack.HotModuleReplacementPlugin(),
);
if (config.apiHost) { webpackConfig.devServer = {
webpackConfig.devServer = { host: 'localhost',
host: 'localhost', port: 8080,
port: 8080, proxy: config.apiHost && {
proxy: { '/api': {
'/api': { target: config.apiHost,
target: config.apiHost, changeOrigin: true, // add host http-header, based on target
changeOrigin: true, // add host http-header, based on target secure: false // allow self-signed certs
secure: false // allow self-signed certs }
} },
}, hot: true,
hot: true, inline: true,
inline: true, historyApiFallback: true
historyApiFallback: true };
};
}
} }
if (isCspEnabled) { if (isCspEnabled) {
webpackConfig.plugins.push(new CSPPlugin({ webpackConfig.plugins.push(
'default-src': '\'none\'', new CSPPlugin({
'style-src': ['\'self\'', '\'unsafe-inline\''], 'default-src': '\'none\'',
'script-src': [ 'style-src': ['\'self\'', '\'unsafe-inline\''],
'\'self\'', 'script-src': [
'\'nonce-edge-must-die\'', '\'self\'',
'\'unsafe-inline\'', '\'nonce-edge-must-die\'',
'https://www.google-analytics.com', '\'unsafe-inline\'',
'https://recaptcha.net/recaptcha/', 'https://www.google-analytics.com',
'https://www.gstatic.com/recaptcha/', 'https://recaptcha.net/recaptcha/',
'https://www.gstatic.cn/recaptcha/', 'https://www.gstatic.com/recaptcha/',
], 'https://www.gstatic.cn/recaptcha/'
'img-src': ['\'self\'', 'data:', 'www.google-analytics.com'], ],
'font-src': ['\'self\'', 'data:'], 'img-src': ['\'self\'', 'data:', 'www.google-analytics.com'],
'connect-src': ['\'self\'', 'https://sentry.ely.by'].concat(isProduction ? [] : ['ws://localhost:8080']), 'font-src': ['\'self\'', 'data:'],
'frame-src': [ 'connect-src': ['\'self\'', 'https://sentry.ely.by'].concat(
'https://www.google.com/recaptcha/', isProduction ? [] : ['ws://localhost:8080']
'https://recaptcha.net/recaptcha/', ),
], 'frame-src': [
'report-uri': 'https://sentry.ely.by/api/2/csp-report/?sentry_key=088e7718236a4f91937a81fb319a93f6', 'https://www.google.com/recaptcha/',
})); 'https://recaptcha.net/recaptcha/'
],
'report-uri':
'https://sentry.ely.by/api/2/csp-report/?sentry_key=088e7718236a4f91937a81fb319a93f6'
})
);
} }
if (isDockerized) { 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; module.exports = webpackConfig;

View File

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

3175
yarn.lock

File diff suppressed because it is too large Load Diff