mirror of
https://github.com/elyby/emails-renderer.git
synced 2024-12-22 13:19:45 +05:30
Hello World
This commit is contained in:
parent
7e8e198763
commit
7e618b23df
9
.babelrc
Normal file
9
.babelrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"presets": ["react", "es2015", "stage-0"],
|
||||
"plugins": ["transform-runtime", ["react-intl", {"messagesDir": "./dist/messages/"}]],
|
||||
"env": {
|
||||
"development": {
|
||||
"presets": ["react-hmre"]
|
||||
}
|
||||
}
|
||||
}
|
213
.eslintrc.json
Normal file
213
.eslintrc.json
Normal file
@ -0,0 +1,213 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"modules": true,
|
||||
"classes": true,
|
||||
"defaultParams": true,
|
||||
"destructuring": true,
|
||||
"spread": true,
|
||||
"arrowFunctions": true,
|
||||
"blockBindings": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"restParams": true,
|
||||
"superInFunctions": true,
|
||||
"templateStrings": true,
|
||||
"experimentalObjectRestSpread": true
|
||||
},
|
||||
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"extends": "eslint:recommended",
|
||||
|
||||
// @see: http://eslint.org/docs/rules/
|
||||
"rules": {
|
||||
// possible errors (including eslint:recommended)
|
||||
"valid-jsdoc": ["warn", {
|
||||
"requireParamDescription": false,
|
||||
"requireReturn": false,
|
||||
"requireReturnDescription": false,
|
||||
"prefer": {
|
||||
"returns": "return"
|
||||
},
|
||||
"preferType": {
|
||||
"String": "string",
|
||||
"Object": "object",
|
||||
"Number": "number",
|
||||
"Function": "function"
|
||||
}
|
||||
}],
|
||||
|
||||
// best practice
|
||||
"block-scoped-var": "error",
|
||||
"curly": "error",
|
||||
"default-case": "error",
|
||||
"dot-location": ["error", "property"],
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-alert": "error",
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty-pattern": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "warn",
|
||||
"no-fallthrough": "error",
|
||||
"no-floating-decimal": "warn",
|
||||
"no-implied-eval": "error",
|
||||
"no-invalid-this": "off",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "warn",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-native-reassign": "error",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-new": "warn",
|
||||
"no-octal-escape": "warn",
|
||||
"no-octal": "error",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": "warn",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unused-expressions": ["warn", {"allowShortCircuit": true, "allowTernary": true}],
|
||||
"no-useless-call": "warn",
|
||||
"no-useless-concat": "warn",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"radix": "error",
|
||||
"wrap-iife": "error",
|
||||
"yoda": "warn",
|
||||
"no-constant-condition": "error",
|
||||
|
||||
// strict mode
|
||||
"strict": ["warn", "never"], // babel все сделает за нас
|
||||
|
||||
// variables
|
||||
"no-catch-shadow": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-label-var": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-shadow": "off",
|
||||
"no-undef-init": "error",
|
||||
"no-undef": "error",
|
||||
"no-use-before-define": ["warn", "nofunc"],
|
||||
|
||||
// CommonJS
|
||||
"no-mixed-requires": "warn",
|
||||
"no-path-concat": "warn",
|
||||
|
||||
// stylistic
|
||||
"array-bracket-spacing": "error",
|
||||
"block-spacing": ["error", "never"],
|
||||
"brace-style": ["error", "1tbs", {"allowSingleLine": true}],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
"comma-dangle": ["warn", "only-multiline"],
|
||||
"computed-property-spacing": "error",
|
||||
"consistent-this": ["error", "that"],
|
||||
"camelcase": "warn",
|
||||
"eol-last": "warn",
|
||||
"id-length": ["error", {"min": 2, "exceptions": ["x", "y", "i", "$"]}],
|
||||
"indent": ["error", 4, {"SwitchCase": 1}],
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": ["error", {"mode": "minimum"}],
|
||||
"linebreak-style": "error",
|
||||
"max-depth": "error",
|
||||
"new-cap": "error",
|
||||
"new-parens": "error",
|
||||
"no-array-constructor": "warn",
|
||||
"no-bitwise": "warn",
|
||||
"no-lonely-if": "error",
|
||||
"no-negated-condition": "warn",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new-object": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-unneeded-ternary": "warn",
|
||||
"one-var": ["error", "never"],
|
||||
"operator-assignment": ["warn", "always"],
|
||||
"operator-linebreak": ["error", "before"],
|
||||
"padded-blocks": ["warn", "never"],
|
||||
"quote-props": ["warn", "as-needed"],
|
||||
"quotes": ["warn", "single"],
|
||||
"semi": "error",
|
||||
"semi-spacing": "error",
|
||||
"keyword-spacing": "warn",
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"space-in-parens": "warn",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "warn",
|
||||
|
||||
// es6
|
||||
"arrow-body-style": "warn",
|
||||
"arrow-parens": "error",
|
||||
"arrow-spacing": "error",
|
||||
"constructor-super": "error",
|
||||
"generator-star-spacing": "warn",
|
||||
"no-class-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-var": "warn",
|
||||
"object-shorthand": "warn",
|
||||
"prefer-arrow-callback": "warn",
|
||||
"prefer-const": "warn",
|
||||
"prefer-reflect": "warn",
|
||||
"prefer-spread": "warn",
|
||||
"prefer-template": "warn",
|
||||
"require-yield": "error",
|
||||
|
||||
// react
|
||||
"react/display-name": "warn",
|
||||
"react/forbid-prop-types": "warn",
|
||||
"react/jsx-boolean-value": "warn",
|
||||
"react/jsx-closing-bracket-location": "warn",
|
||||
"react/jsx-curly-spacing": "warn",
|
||||
"react/jsx-handler-names": ["warn", {"eventHandlerPrefix": "on", "eventHandlerPropPrefix": "on"}],
|
||||
"react/jsx-indent-props": "warn",
|
||||
"react/jsx-key": "warn",
|
||||
"react/jsx-max-props-per-line": ["warn", {"maximum": 3}],
|
||||
"react/jsx-no-bind": "warn",
|
||||
"react/jsx-no-duplicate-props": "warn",
|
||||
"react/jsx-no-literals": "warn",
|
||||
"react/jsx-no-undef": "warn",
|
||||
"react/jsx-pascal-case": "warn",
|
||||
"react/jsx-uses-react": "warn",
|
||||
"react/jsx-uses-vars": "warn",
|
||||
"react/jsx-no-comment-textnodes": "warn",
|
||||
"react/jsx-wrap-multilines": "warn",
|
||||
"react/no-deprecated": "warn",
|
||||
"react/no-did-mount-set-state": "warn",
|
||||
"react/no-did-update-set-state": "warn",
|
||||
"react/no-direct-mutation-state": "warn",
|
||||
"react/require-render-return": "warn",
|
||||
"react/no-is-mounted": "warn",
|
||||
"react/no-multi-comp": "warn",
|
||||
"react/no-string-refs": "warn",
|
||||
"react/no-unknown-property": "warn",
|
||||
"react/prefer-es6-class": "warn",
|
||||
"react/prop-types": "warn",
|
||||
"react/react-in-jsx-scope": "warn",
|
||||
"react/self-closing-comp": "warn",
|
||||
"react/sort-comp": ["warn", {"order": ["lifecycle", "render", "everything-else"]}]
|
||||
}
|
||||
}
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
74
package.json
Normal file
74
package.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "email-renderer",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@gitlab.com/elyby/email-renderer.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "SleepWalker <dev@udf.su>",
|
||||
"license": "private",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/elyby/email-renderer/issues"
|
||||
},
|
||||
"homepage": "https://gitlab.com/elyby/email-renderer#README",
|
||||
"scripts": {
|
||||
"start": "rm -rf dist/ && webpack-dev-server --progress --colors",
|
||||
"up": "npm update",
|
||||
"test": "karma start ./karma.conf.js",
|
||||
"lint": "eslint ./src",
|
||||
"i18n": "cd ./scripts && ./node_modules/.bin/babel-node i18n-collect.js",
|
||||
"build": "rm -rf dist/ && webpack --progress --colors -p",
|
||||
"render": ""
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.3.14",
|
||||
"classnames": "^2.1.3",
|
||||
"history": "^2.0.0",
|
||||
"intl": "^1.2.2",
|
||||
"intl-format-cache": "^2.0.4",
|
||||
"intl-messageformat": "^1.1.0",
|
||||
"react": "^15.0.0",
|
||||
"react-dom": "^15.0.0",
|
||||
"react-intl": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^6.0.0",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-plugin-react-intl": "^2.0.0",
|
||||
"babel-plugin-transform-runtime": "^6.3.13",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-react-hmre": "^1.0.1",
|
||||
"babel-preset-stage-0": "^6.3.13",
|
||||
"babel-runtime": "^6.0.0",
|
||||
"bundle-loader": "^0.5.4",
|
||||
"css-loader": "^0.23.0",
|
||||
"eslint": "^3.1.1",
|
||||
"eslint-plugin-react": "^6.0.0",
|
||||
"exports-loader": "^0.6.3",
|
||||
"extract-text-webpack-plugin": "^1.0.0",
|
||||
"file-loader": "^0.9.0",
|
||||
"html-loader": "^0.4.3",
|
||||
"html-webpack-plugin": "^2.0.0",
|
||||
"imports-loader": "^0.6.5",
|
||||
"json-loader": "^0.5.4",
|
||||
"loader-utils": "^0.2.15",
|
||||
"node-sass": "^3.4.2",
|
||||
"postcss-import": "^8.1.2",
|
||||
"postcss-loader": "^0.9.0",
|
||||
"postcss-scss": "^0.1.8",
|
||||
"postcss-url": "SleepWalker/postcss-url#switch-to-async-api",
|
||||
"raw-loader": "^0.5.1",
|
||||
"sass-loader": "^4.0.0",
|
||||
"scripts": "file:scripts",
|
||||
"style-loader": "^0.13.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"webpack": "^1.12.9",
|
||||
"webpack-dev-server": "^1.14.0",
|
||||
"webpack-utils": "file:webpack-utils"
|
||||
}
|
||||
}
|
3
scripts/.babelrc
Normal file
3
scripts/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"breakConfig": true
|
||||
}
|
183
scripts/i18n-collect.js
Normal file
183
scripts/i18n-collect.js
Normal file
@ -0,0 +1,183 @@
|
||||
/* eslint-disable no-console */
|
||||
import fs from 'fs';
|
||||
import {sync as globSync} from 'glob';
|
||||
import {sync as mkdirpSync} from 'mkdirp';
|
||||
import chalk from 'chalk';
|
||||
import prompt from 'prompt';
|
||||
|
||||
const MESSAGES_PATTERN = '../dist/messages/**/*.json';
|
||||
const LANG_DIR = '../src/i18n';
|
||||
const DEFAULT_LOCALE = 'en';
|
||||
const SUPPORTED_LANGS = [DEFAULT_LOCALE].concat('ru', 'be', 'uk');
|
||||
|
||||
/**
|
||||
* Aggregates the default messages that were extracted from the app's
|
||||
* React components via the React Intl Babel plugin. An error will be thrown if
|
||||
* there are messages in different components that use the same `id`. The result
|
||||
* is a flat collection of `id: message` pairs for the app's default locale.
|
||||
*/
|
||||
let idToFileMap = {};
|
||||
let duplicateIds = [];
|
||||
const collectedMessages = globSync(MESSAGES_PATTERN)
|
||||
.map((filename) => [filename, JSON.parse(fs.readFileSync(filename, 'utf8'))])
|
||||
.reduce((collection, [file, descriptors]) => {
|
||||
descriptors.forEach(({id, defaultMessage}) => {
|
||||
if (collection.hasOwnProperty(id)) {
|
||||
duplicateIds.push(id);
|
||||
}
|
||||
|
||||
collection[id] = defaultMessage;
|
||||
idToFileMap[id] = (idToFileMap[id] || []).concat(file);
|
||||
});
|
||||
|
||||
return collection;
|
||||
}, {});
|
||||
|
||||
if (duplicateIds.length) {
|
||||
console.log('\nFound duplicated ids:');
|
||||
duplicateIds.forEach((id) => console.log(`${chalk.yellow(id)}:\n - ${idToFileMap[id].join('\n - ')}\n`));
|
||||
console.log(chalk.red('Please correct the errors above to proceed further!'));
|
||||
return;
|
||||
}
|
||||
|
||||
duplicateIds = null;
|
||||
idToFileMap = null;
|
||||
|
||||
/**
|
||||
* Making a diff with the previous DEFAULT_LOCALE version
|
||||
*/
|
||||
const defaultMessagesPath = `${LANG_DIR}/${DEFAULT_LOCALE}.json`;
|
||||
let keysToUpdate = [];
|
||||
let keysToAdd = [];
|
||||
let keysToRemove = [];
|
||||
const keysToRename = [];
|
||||
const isNotMarked = (value) => value.slice(0, 2) !== '--';
|
||||
try {
|
||||
const prevMessages = JSON.parse(fs.readFileSync(defaultMessagesPath, 'utf8'));
|
||||
const prevMessagesMap = Object.entries(prevMessages).reduce((acc, [key, value]) => {
|
||||
if (acc[value]) {
|
||||
acc[value].push(key);
|
||||
} else {
|
||||
acc[value] = [key];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
keysToAdd = Object.keys(collectedMessages).filter((key) => !prevMessages[key]);
|
||||
keysToRemove = Object.keys(prevMessages).filter((key) => !collectedMessages[key]).filter(isNotMarked);
|
||||
keysToUpdate = Object.entries(prevMessages).reduce((acc, [key, message]) =>
|
||||
acc.concat(collectedMessages[key] && collectedMessages[key] !== message ? key : [])
|
||||
, []);
|
||||
|
||||
// detect keys to rename, mutating keysToAdd and keysToRemove
|
||||
[].concat(keysToAdd).forEach((toKey) => {
|
||||
const keys = prevMessagesMap[collectedMessages[toKey]] || [];
|
||||
const fromKey = keys.find((fromKey) => keysToRemove.indexOf(fromKey) > -1);
|
||||
|
||||
if (fromKey) {
|
||||
keysToRename.push([fromKey, toKey]);
|
||||
|
||||
keysToRemove.splice(keysToRemove.indexOf(fromKey), 1);
|
||||
keysToAdd.splice(keysToAdd.indexOf(toKey), 1);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(chalk.yellow(`Can not read ${defaultMessagesPath}. The new file will be created.`), err);
|
||||
}
|
||||
|
||||
if (!keysToAdd.length && !keysToRemove.length && !keysToUpdate.length && !keysToRename.length) {
|
||||
return console.log(chalk.green('Everything is up to date!'));
|
||||
}
|
||||
|
||||
console.log(chalk.magenta(`The diff relative to default locale (${DEFAULT_LOCALE}) is:`));
|
||||
|
||||
if (keysToRemove.length) {
|
||||
console.log('The following keys will be removed:');
|
||||
console.log([chalk.red('\n - '), keysToRemove.join(chalk.red('\n - ')), '\n'].join(''));
|
||||
}
|
||||
|
||||
if (keysToAdd.length) {
|
||||
console.log('The following keys will be added:');
|
||||
console.log([chalk.green('\n + '), keysToAdd.join(chalk.green('\n + ')), '\n'].join(''));
|
||||
}
|
||||
|
||||
if (keysToUpdate.length) {
|
||||
console.log('The following keys will be updated:');
|
||||
console.log([chalk.yellow('\n @ '), keysToUpdate.join(chalk.yellow('\n @ ')), '\n'].join(''));
|
||||
}
|
||||
|
||||
if (keysToRename.length) {
|
||||
console.log('The following keys will be renamed:\n');
|
||||
console.log(keysToRename.reduce((str, pair) =>
|
||||
[str, pair[0], chalk.yellow(' -> '), pair[1], '\n'].join('')
|
||||
, ''));
|
||||
}
|
||||
|
||||
prompt.start();
|
||||
prompt.get({
|
||||
properties: {
|
||||
apply: {
|
||||
description: 'Apply changes? [Y/n]',
|
||||
pattern: /^y|n$/i,
|
||||
message: 'Please enter "y" or "n"',
|
||||
default: 'y',
|
||||
before: (value) => value.toLowerCase() === 'y'
|
||||
}
|
||||
}
|
||||
}, (err, resp) => {
|
||||
console.log('\n');
|
||||
|
||||
if (err || !resp.apply) {
|
||||
return console.log(chalk.red('Aborted'));
|
||||
}
|
||||
|
||||
buildLocales();
|
||||
|
||||
console.log(chalk.green('All locales was successfuly built'));
|
||||
});
|
||||
|
||||
|
||||
function buildLocales() {
|
||||
mkdirpSync(LANG_DIR);
|
||||
|
||||
SUPPORTED_LANGS.map((lang) => {
|
||||
const destPath = `${LANG_DIR}/${lang}.json`;
|
||||
|
||||
let newMessages = {};
|
||||
try {
|
||||
newMessages = JSON.parse(fs.readFileSync(destPath, 'utf8'));
|
||||
} catch (err) {
|
||||
console.log(chalk.yellow(`Can not read ${destPath}. The new file will be created.`), err);
|
||||
}
|
||||
|
||||
keysToRename.forEach(([fromKey, toKey]) => {
|
||||
newMessages[toKey] = newMessages[fromKey];
|
||||
delete newMessages[fromKey];
|
||||
});
|
||||
keysToRemove.forEach((key) => {
|
||||
delete newMessages[key];
|
||||
});
|
||||
keysToUpdate.forEach((key) => {
|
||||
newMessages[`--${key}`] = newMessages[key];
|
||||
newMessages[key] = collectedMessages[key];
|
||||
});
|
||||
keysToAdd.forEach((key) => {
|
||||
newMessages[key] = collectedMessages[key];
|
||||
});
|
||||
|
||||
const sortedKeys = Object.keys(newMessages).sort((key1, key2) => {
|
||||
key1 = key1.replace(/^\-+/, '');
|
||||
key2 = key2.replace(/^\-+/, '');
|
||||
|
||||
return key1 < key2 || !isNotMarked(key1) ? -1 : 1;
|
||||
});
|
||||
|
||||
const sortedNewMessages = sortedKeys.reduce((acc, key) => {
|
||||
acc[key] = newMessages[key];
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
fs.writeFileSync(destPath, JSON.stringify(sortedNewMessages, null, 4) + '\n');
|
||||
});
|
||||
}
|
17
scripts/package.json
Normal file
17
scripts/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "scripts",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "i18n-build.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "^1.1.3",
|
||||
"node-babel": "^0.1.2",
|
||||
"prompt": "^1.0.0"
|
||||
}
|
||||
}
|
BIN
src/favicon.ico
Normal file
BIN
src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 B |
24
src/index.ejs
Normal file
24
src/index.ejs
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Ely.by - Email Renderer</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||||
<% if (htmlWebpackPlugin.files.favicon) { %>
|
||||
<link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon %>">
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app" class="app"></div>
|
||||
|
||||
<% for (var css in htmlWebpackPlugin.files.css) { %>
|
||||
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
|
||||
<% } %>
|
||||
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
13
src/index.js
Normal file
13
src/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import 'babel-polyfill';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
ReactDOM.render(
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<div>Hello world</div>
|
||||
</IntlProvider>,
|
||||
document.getElementById('app')
|
||||
);
|
36
webpack-utils/cssUrl.js
Normal file
36
webpack-utils/cssUrl.js
Normal file
@ -0,0 +1,36 @@
|
||||
// при использовании 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}
|
||||
`) + urlPostfix(url)
|
||||
)
|
||||
) : resolve(url)
|
||||
)
|
||||
});
|
||||
};
|
21
webpack-utils/intl-loader.js
Normal file
21
webpack-utils/intl-loader.js
Normal file
@ -0,0 +1,21 @@
|
||||
module.exports = function() {
|
||||
this.cacheable && this.cacheable();
|
||||
|
||||
var moduleId = this.context
|
||||
.replace(this.options.resolve.root, '')
|
||||
.replace(/^\/|\/$/g, '')
|
||||
.replace(/\//g, '.');
|
||||
|
||||
var content = this.inputValue[0];
|
||||
content = JSON.stringify(Object.keys(content).reduce(function(translations, key) {
|
||||
translations[key] = {
|
||||
id: moduleId + '.' + key,
|
||||
defaultMessage: content[key]
|
||||
};
|
||||
|
||||
return translations;
|
||||
}, {}));
|
||||
|
||||
return 'import { defineMessages } from \'react-intl\';'
|
||||
+ 'export default defineMessages(' + content + ')';
|
||||
};
|
10
webpack-utils/package.json
Normal file
10
webpack-utils/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "webpack-utils",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"loader-utils": "^0.2.12"
|
||||
}
|
||||
}
|
232
webpack.config.js
Normal file
232
webpack.config.js
Normal file
@ -0,0 +1,232 @@
|
||||
/* eslint-env node */
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var webpack = require('webpack');
|
||||
var loaderUtils = require('loader-utils');
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var cssUrl = require('webpack-utils/cssUrl');
|
||||
var cssImport = require('postcss-import');
|
||||
|
||||
var vendor = Object.keys(require('./package.json').dependencies);
|
||||
|
||||
const rootPath = path.resolve('./src');
|
||||
|
||||
const isProduction = process.argv.some((arg) => arg === '-p');
|
||||
|
||||
process.env.NODE_ENV = isProduction ? 'production' : 'development';
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
var webpackConfig = {
|
||||
entry: {
|
||||
app: path.join(__dirname, 'src'),
|
||||
vendor: vendor
|
||||
},
|
||||
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
publicPath: '/',
|
||||
filename: '[name].js?[hash]'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
root: rootPath,
|
||||
extensions: ['', '.js', '.jsx']
|
||||
},
|
||||
|
||||
devServer: {
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
// proxy: {
|
||||
// '/api*': {
|
||||
// headers: {
|
||||
// host: config.apiHost.replace(/https?:|\//g, '')
|
||||
// },
|
||||
// target: config.apiHost
|
||||
// }
|
||||
// },
|
||||
hot: true,
|
||||
inline: true,
|
||||
historyApiFallback: true
|
||||
},
|
||||
|
||||
devtool: 'eval',
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
|
||||
},
|
||||
__DEV__: !isProduction,
|
||||
__PROD__: isProduction
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'src/index.ejs',
|
||||
favicon: 'src/favicon.ico',
|
||||
hash: isProduction,
|
||||
filename: 'index.html',
|
||||
inject: false,
|
||||
minify: {
|
||||
collapseWhitespace: isProduction
|
||||
}
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
// window.fetch polyfill
|
||||
fetch: 'imports?this=>self!exports?self.fetch!whatwg-fetch'
|
||||
}),
|
||||
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js?[hash]')
|
||||
].concat(isProduction ? [
|
||||
new webpack.optimize.DedupePlugin(),
|
||||
new webpack.optimize.UglifyJsPlugin()
|
||||
] : []),
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.scss$/,
|
||||
extractInProduction: true,
|
||||
loader: 'style!css?' + JSON.stringify(cssLoaderQuery) + '!sass!postcss?syntax=postcss-scss'
|
||||
},
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel'
|
||||
},
|
||||
{
|
||||
test: /\.(png|gif|jpg|svg)$/,
|
||||
loader: 'file',
|
||||
query: {
|
||||
name: 'assets/[name].[ext]?[hash]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|ttf)$/,
|
||||
loader: 'file',
|
||||
query: {
|
||||
name: 'assets/fonts/[name].[ext]?[hash]'
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
exclude: /(intl|font)\.json/,
|
||||
loader: 'json'
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'html'
|
||||
},
|
||||
{
|
||||
test: /\.intl\.json$/,
|
||||
loader: 'babel!intl!json'
|
||||
},
|
||||
{
|
||||
test: /\.font\.(js|json)$/,
|
||||
loader: 'raw!fontgen'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
resolveLoader: {
|
||||
alias: {
|
||||
intl: path.resolve('webpack-utils/intl-loader')
|
||||
}
|
||||
},
|
||||
|
||||
postcss() {
|
||||
return [
|
||||
cssImport({
|
||||
path: rootPath,
|
||||
addDependencyTo: webpack,
|
||||
|
||||
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))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return fileCache[filename];
|
||||
}
|
||||
|
||||
return defaultLoad(filename, importOptions);
|
||||
}
|
||||
)(require('postcss-import/lib/load-content'))
|
||||
}),
|
||||
|
||||
cssUrl(this)
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
if (isProduction) {
|
||||
webpackConfig.module.loaders.forEach((loader) => {
|
||||
if (loader.extractInProduction) {
|
||||
// remove style-loader from chain and pass through ExtractTextPlugin
|
||||
const parts = loader.loader.split('!');
|
||||
|
||||
loader.loader = ExtractTextPlugin.extract(
|
||||
parts[0], // style-loader
|
||||
parts.slice(1) // css-loader and rest
|
||||
.join('!')
|
||||
.replace(/[&?]sourcemap/, '')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
webpackConfig.plugins.push(new ExtractTextPlugin('styles.css', {
|
||||
allChunks: true
|
||||
}));
|
||||
|
||||
webpackConfig.devtool = false;
|
||||
}
|
||||
|
||||
if (!isProduction) {
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = webpackConfig;
|
Loading…
Reference in New Issue
Block a user