#56: switch to self-hosted fonts. Improve css urls resolving. Update outdated dependencies

This commit is contained in:
SleepWalker 2016-07-22 23:58:31 +03:00
parent d36e771c4f
commit fc84fb49c6
16 changed files with 151 additions and 58 deletions

View File

@ -51,9 +51,8 @@
"bundle-loader": "^0.5.4",
"chai": "^3.0.0",
"css-loader": "^0.23.0",
"cssnano": "^3.4.0",
"enzyme": "^2.2.0",
"eslint": "^2.11.0",
"eslint": "^3.1.1",
"eslint-plugin-react": "^5.0.0",
"exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0",
@ -71,11 +70,14 @@
"karma-sinon": "^1.0.4",
"karma-sourcemap-loader": "*",
"karma-webpack": "^1.5.1",
"loader-utils": "^0.2.15",
"mocha": "^2.2.5",
"node-sass": "^3.4.2",
"phantomjs-prebuilt": "^2.0.0",
"postcss-import": "^8.1.2",
"postcss-loader": "^0.9.0",
"postcss-url": "^5.1.1",
"postcss-scss": "^0.1.8",
"postcss-url": "SleepWalker/postcss-url#switch-to-async-api",
"raw-loader": "^0.5.1",
"react-addons-test-utils": "^15.0.2",
"redux-devtools": "^3.3.1",

View File

@ -84,25 +84,25 @@
.langEn {
composes: langIco;
background-image: url('icons/flag_en.svg');
background-image: url('~icons/flag_en.svg');
}
.langRu {
composes: langIco;
background-image: url('icons/flag_ru.svg');
background-image: url('~icons/flag_ru.svg');
}
.langBe {
composes: langIco;
background-image: url('icons/flag_be.svg');
background-image: url('~icons/flag_be.svg');
}
.langUk {
composes: langIco;
background-image: url('icons/flag_uk.svg');
background-image: url('~icons/flag_uk.svg');
}
.trigger {

View File

@ -1,4 +1,3 @@
// TODO: should we host fonts from our side?
$font-family-title: 'Roboto Condensed', Arial, sans-serif;
$font-family-base: 'Roboto', Arial, sans-serif;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -16,8 +16,6 @@
<%= require('first-render').default %>
<link href="//fonts.googleapis.com/css?family=Roboto:400,500&subset=latin,cyrillic" rel="stylesheet" type="text/css">
<link href="//fonts.googleapis.com/css?family=Roboto+Condensed&subset=latin,cyrillic" rel="stylesheet" type="text/css">
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>

View File

@ -50,3 +50,33 @@ label,
a {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
// Declare fonts here, to be sure that want be imported more than once
// @see https://github.com/majodev/google-webfonts-helper to download some other fonts
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: local('Roboto Condensed'), local('RobotoCondensed-Regular'),
url('~fonts/roboto-condensed/roboto-condensed-v13-cyrillic_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('~fonts/roboto-condensed/roboto-condensed-v13-cyrillic_latin-regular.woff') format('woff'), /* Modern Browsers */
url('~fonts/roboto-condensed/roboto-condensed-v13-cyrillic_latin-regular.ttf') format('truetype'); /* Safari, Android, iOS */
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'),
url('~fonts/roboto/roboto-v15-cyrillic_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('~fonts/roboto/roboto-v15-cyrillic_latin-regular.woff') format('woff'), /* Modern Browsers */
url('~fonts/roboto/roboto-v15-cyrillic_latin-regular.ttf') format('truetype'); /* Safari, Android, iOS */
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'),
url('~fonts/roboto/roboto-v15-cyrillic_latin-500.woff2') format('woff2'), /* Super Modern Browsers */
url('~fonts/roboto/roboto-v15-cyrillic_latin-500.woff') format('woff'), /* Modern Browsers */
url('~fonts/roboto/roboto-v15-cyrillic_latin-500.ttf') format('truetype'); /* Safari, Android, iOS */
}

View File

@ -3,14 +3,17 @@
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 cssnano = require('cssnano');
var cssUrl = require("postcss-url");
var cssUrl = require('./webpack/cssUrl');
var cssImport = require('postcss-import');
var iconfontImporter = require('./webpack/node-sass-iconfont-importer');
var vendor = Object.keys(require('./package.json').dependencies);
const rootPath = path.resolve('./src');
/**
* TODO: https://babeljs.io/docs/plugins/
* TODO: отдельные конфиги для env (аля https://github.com/davezuko/react-redux-starter-kit)
@ -24,15 +27,11 @@ var vendor = Object.keys(require('./package.json').dependencies);
* https://github.com/davezuko/react-redux-starter-kit
*/
var isProduction = process.argv.some(function(arg) {
return arg === '-p';
});
const isProduction = process.argv.some((arg) => arg === '-p');
var isTest = process.argv.some(function(arg) {
return arg.indexOf('karma') !== -1;
});
const isTest = process.argv.some((arg) => arg.indexOf('karma') !== -1);
var isDockerized = !!process.env.DOCKERIZED;
const isDockerized = !!process.env.DOCKERIZED;
process.env.NODE_ENV = isProduction ? 'production' : 'development';
if (isTest) {
@ -40,8 +39,8 @@ if (isTest) {
}
const CSS_CLASS_TEMPLATE = isProduction ? '[hash:base64:5]' : '[path][name]-[local]';
var config;
var config;
try {
config = require('./config/dev.json');
} catch (err) {
@ -49,7 +48,29 @@ try {
throw err;
}
var rootPath = path.resolve('./src');
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: {
@ -70,7 +91,7 @@ var webpackConfig = {
externals: isTest ? {
// http://airbnb.io/enzyme/docs/guides/webpack.html
'cheerio': 'window',
cheerio: 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': true,
'react/addons': true
@ -130,7 +151,7 @@ var webpackConfig = {
{
test: /\.scss$/,
extractInProduction: true,
loader: 'style!css?modules&importLoaders=2&localIdentName=' + CSS_CLASS_TEMPLATE + '!postcss!sass'
loader: 'style!css?' + JSON.stringify(cssLoaderQuery) + '!sass!postcss?syntax=postcss-scss'
},
{
test: /\.jsx?$/,
@ -139,7 +160,19 @@ var webpackConfig = {
},
{
test: /\.(png|gif|jpg|svg)$/,
loader: 'url?limit=1000&name=assets/[name].[ext]?[hash]'
loader: 'url',
query: {
limit: 1000,
name: 'assets/[name].[ext]?[hash]'
}
},
{
test: /\.(woff|woff2|eot|ttf)$/, // NOTE: svg is loaded by another loader for now
loader: 'file',
query: {
name: 'assets/fonts/[name].[ext]?[hash]'
}
},
{
test: /\.json$/,
@ -163,7 +196,7 @@ var webpackConfig = {
resolveLoader: {
alias: {
'intl': path.resolve('./webpack/intl-loader')
intl: path.resolve('./webpack/intl-loader')
}
},
@ -173,41 +206,36 @@ var webpackConfig = {
})
},
postcss: [
cssUrl({
url: function(url, decl, from, dirname, to, options, result) {
// scss не правильно резолвит относительные урлы.
// добавляем к урлам остаток пути, что бы они были относительно root
//
// Например:
// file: components/ui/foo.scss
// ./images/foo.png -> components/ui/images/foo.png
postcss() {
// TODO: иконочные шрифты эмитятся > 1 раза
return [
cssImport({
path: rootPath,
addDependencyTo: webpack,
if (url.indexOf('./') === 0) {
var relativeToRoot = dirname.split(rootPath + '/')[1];
resolve: ((defaultResolve) =>
(url, basedir, importOptions) =>
defaultResolve(loaderUtils.urlToRequest(url), basedir, importOptions)
)(require('postcss-import/lib/resolve-id')),
return path.join(relativeToRoot, url);
}
load: ((defaultLoad) =>
(filename, importOptions) => {
if (/\.font.(js|json)$/.test(filename)) {
return new Promise((resolve, reject) =>
this.loadModule(filename, (err, source) =>
err ? reject(err) : resolve(this.exec(source))
)
);
}
return url;
}
}),
cssnano({
// sourcemap: !isProduction,
autoprefixer: {
add: true,
remove: true,
browsers: ['last 2 versions']
},
safe: true,
// отключаем минификацию цветов, что бы она не ломала такие выражения:
// composes: black from './buttons.scss';
colormin: false,
discardComments: {
removeAll: true
}
})
]
return defaultLoad(filename, importOptions);
}
)(require('postcss-import/lib/load-content'))
}),
cssUrl(this)
];
}
};
if (isDockerized) {
@ -220,7 +248,7 @@ if (isDockerized) {
if (isProduction) {
webpackConfig.module.loaders.forEach((loader) => {
if (loader.extractInProduction) {
var parts = loader.loader.split('!');
const parts = loader.loader.split('!');
loader.loader = ExtractTextPlugin.extract(
parts[0],
parts.slice(1)

36
webpack/cssUrl.js Normal file
View 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)
)
});
};