Commit 1a00fd02 by Иван Кубота

SVG inlining

parent cc5e0711
...@@ -50,7 +50,7 @@ const serveBundle = async function(tpl, res){ ...@@ -50,7 +50,7 @@ const serveBundle = async function(tpl, res){
var path = require('path'); var path = require('path');
var bCore = require( "@babel/core" ); var bCore = require( "@babel/core" );
var b = require('@babel/plugin-transform-modules-amd') var simpleTransformToAMD = require('./pack/babel-plugin-transform-2015es-to-amd')
var transformJSX = function(code, fileName, cb) { var transformJSX = function(code, fileName, cb) {
bCore.transform( bCore.transform(
code, code,
...@@ -61,18 +61,7 @@ var transformJSX = function(code, fileName, cb) { ...@@ -61,18 +61,7 @@ var transformJSX = function(code, fileName, cb) {
"pragmaFrag": "D.f", // default is React.Fragment "pragmaFrag": "D.f", // default is React.Fragment
"throwIfNamespace": false // defaults to true "throwIfNamespace": false // defaults to true
} ], } ],
['@babel/plugin-transform-modules-amd'] [simpleTransformToAMD]
/* ["@babel/plugin-transform-modules-commonjs", { "synchronousImport": true }],
['func-wrap', {
/!* use a named export *!/
name: 'library',
/!* assign arguments to the function *!/
args: ['fileName="'+fileName+'"'],
/!* export as CommonJS *!/
format: 'cjs'
}]*/
], ],
sourceMaps: 'both', sourceMaps: 'both',
sourceFileName: fileName, sourceFileName: fileName,
...@@ -96,60 +85,48 @@ var transformServe = function(dir) { ...@@ -96,60 +85,48 @@ var transformServe = function(dir) {
debugger debugger
}*/ }*/
if (req.url.substr(-5) === '.scss') { if (req.url.substr(-5) === '.scss'){
// not secure // not secure
//console.log('scss', dir, req.url, __dirname) //console.log('scss', dir, req.url, __dirname)
fs.readFile(path.join(__dirname,dir,req.url), function(err, data){ fs.readFile( path.join( __dirname, dir, req.url ), function( err, data ){
if( err ){ if( err ){
next(); next();
}else{ }else{
sass.render({ sass.render( {
file: path.join(__dirname,dir,req.url), file: path.join( __dirname, dir, req.url ),
sourceMap: true, sourceMap: true,
importer: function(url, prev, done) { importer: function( url, prev, done ){
//console.log(url, prev, done) setTimeout( function( result ){
// url is the path in import as is, which LibSass encountered. var name = path.resolve( path.dirname( prev ), url );
// prev is the previously resolved path. var displayName = path.relative( path.join( __dirname, dir ), path.resolve( path.dirname( prev ), url ) )
// done is an optional callback, either consume it or return value synchronously.
// this.options contains this options hash done( {
setTimeout(function(result){
var name = path.resolve(path.dirname(prev), url);
var displayName = path.relative(path.join(__dirname,dir),path.resolve(path.dirname(prev), url))
done({
file: displayName, // only one of them is required, see section Special Behaviours. file: displayName, // only one of them is required, see section Special Behaviours.
contents: fs.readFileSync(name)+'' contents: fs.readFileSync( name ) + ''
}); } );
//console.log({name, __dirname, dir, url, prev}) }, 10 );
},10);
// OR
//var result = someSyncFunction(url, prev);
//return {file: result.path, contents: result.data};
} }
}, function(err, result) { }, function( err, result ){
if(err){ if( err ){
const errorText = `Error at ${err.file}:\n`+err.formatted; const errorText = `Error at ${err.file}:\n` + err.formatted;
return res.end(errorText) return res.end( errorText )
} }
res.header('Content-Type', 'text/css'); res.header( 'Content-Type', 'text/css' );
res.end(result.css) res.end( result.css )
//debugger } );
/*...*/ });
} }
}); } );
}else if (req.url.substr(-4) === '.jsx') { }else if (req.url.substr(-4) === '.jsx') {
console.log('Serve jsx', dir, req.url)
fs.readFile(path.join(dir, req.url), function(err, data){ fs.readFile(path.join(dir, req.url), function(err, data){
if( err ){ if( err ){
next(); next();
}else{ }else{
console.log('Transform jsx', req.url); console.log('Transform jsx', req.url);
transformJSX(data+'', req.url, function(err, result) { transformJSX(data+'', req.url, function(err, result) {
if(err){ if(err){
console.log('Error in transforming jsx', err); console.log('Error in transforming jsx', err);
res.end(err.message+'\n'+err.stack) res.end(err.message+'\n'+err.stack)
}else{ }else{
cache[ req.url + '.map' ] = JSON.stringify( result.map ); cache[ req.url + '.map' ] = JSON.stringify( result.map );
...@@ -160,6 +137,42 @@ var transformServe = function(dir) { ...@@ -160,6 +137,42 @@ var transformServe = function(dir) {
} }
}); });
}else if (req.url.substr(-4) === '.svg') {
console.log('Serve svg', dir, req.url)
fs.readFile(path.join(dir, req.url), function(err, data){
if( err ){
next();
}else{
console.log('Generate SVG declaration', req.url);
bCore.transform(
`export default D.declare("${req.url.replace(/\//g,'.').split('.').filter(String).join('.')}", (cfg)=>{ return ${(data+'').replace(/(<svg[^>]+"\s*)>/i,'$1 {...cfg}>')}; });`,
{
"plugins": [
[ "@babel/plugin-transform-react-jsx", {
"pragma": "D.s", // default pragma is React.createElement
"throwIfNamespace": false // defaults to true
} ],
[simpleTransformToAMD]
],
sourceMaps: 'both',
sourceFileName: req.url,
moduleId: req.url
}, function( err, result ){
if(err){
console.log('Error in transforming jsx', err);
res.end(err.message+'\n'+err.stack)
}else{
cache[ req.url + '.map' ] = JSON.stringify( result.map );
res.set( 'SourceMap', req.url + '.map' );
res.header( 'Content-Type', 'text/javascript' );
res.end( result.code );
}
} );
}
});
} else { } else {
next(); next();
} }
......
const template = require("@babel/template");
const buildModule = template.default(`
define(MODULE_NAME, IMPORT_PATHS, function(IMPORT_VARS) {
NAMED_IMPORTS;
BODY;
});
`);
var _core = require("@babel/core");
module.exports = function transformToAmd({types: t}) {
return {
visitor: {
Program: {
exit(programPath) {
const bodyPaths = programPath.get("body");
const sources = [];
const anonymousSources = [];
const vars = [];
const namedImports = [];
let isModular = false;
let defaultExportExpression = null;
let moduleName = this.getModuleName();
if (moduleName) moduleName = _core.types.stringLiteral(moduleName);
for (let i = 0; i < bodyPaths.length; i++) {
const isLastBodyStatement = i === bodyPaths.length - 1;
const bodyStatementPath = bodyPaths[i];
if (t.isExportNamedDeclaration(bodyStatementPath)) {
// console.log("named export");
const {specifiers} = bodyStatementPath.node;
let j = specifiers.length;
while (j--) {
const specifier = specifiers[j];
if (
t.isExportSpecifier(specifier) &&
specifier.exported.name === "default"
) {
// import -> export
// export {name as default, ...};
// export { default } from ....;
// export { default as default } from ....;
// Not supported:
// export {name as default, ...} from ....;
if(specifier.local.name === "default") {
// console.log("export { default as default, ... } from ...;");
defaultExportExpression = bodyStatementPath.scope.generateUidIdentifier(
bodyStatementPath.node.source.value
);
sources.push(bodyStatementPath.node.source);
vars.push(defaultExportExpression);
} else if(bodyStatementPath.node.source) {
// console.log(`export { ${specifier.local.name} as default, ... } from ...;`);
throw new Error("Named imports are not supported.");
} else {
// console.log(`export { ${specifier.local.name} as default, ... };`);
defaultExportExpression = specifier.local;
}
specifiers.splice(j, 1);
isModular = true;
break;
} else if (t.isExportDefaultSpecifier(specifier)) {
// console.log("export { default } from ...;");
defaultExportExpression = specifier.exported;
specifiers.splice(j, 1);
isModular = true;
break;
}
}
if (specifiers.length === 0) {
bodyStatementPath.remove();
}
} else if (t.isExportDefaultDeclaration(bodyStatementPath)) {
// console.log("export default x;");
// Check if a variable is needed. Yes if:
// - it's a function declaration
// - it's a class declaration
// - it's an expression, which could, in principle, be embedded in
// the return declaration, however, not if this is not the last
// body statement.
const declaration = bodyStatementPath.get("declaration");
if (
t.isFunctionDeclaration(declaration) ||
t.isClassDeclaration(declaration)
) {
// Does the class or function have a name?
if (declaration.node.id !== null) {
// > export default class foo { ... };
// console.log("Named class or function isFun=" + t.isFunctionDeclaration(declaration));
// Replace the export with the actual declaration.
// > class foo { ... };
bodyStatementPath.replaceWith(declaration.node);
// The return value will be the name of the class or function.
defaultExportExpression = declaration.node.id;
} else {
// > export default class { ... };
// console.log("Anonymous class or function isFun=" + t.isFunctionDeclaration(declaration));
// Need a variable to capture the class or function, as an expression.
// > var export_default = class { ... };
const varName = createDefaultExportVarName(bodyStatementPath.scope);
// The variable will contain the anonymous declaration, but as an expression.
const varValue = declaration.node;
// Change the declaration to a corresponding expression.
varValue.type = t.isFunctionDeclaration(declaration)
? "FunctionExpression"
: "ClassExpression";
const variable = createVariable(t, varName, varValue);
bodyStatementPath.replaceWith(variable);
// The variable will be the function's return value.
// > return export_default;
defaultExportExpression = varName;
}
} else if (!isLastBodyStatement) {
// console.log("expression, not last in body");
// An expression that needs to be captured as a variable.
// Need a variable to capture the class or function, as an expression.
// > var export_default = class { ... };
const varName = createDefaultExportVarName(bodyStatementPath.scope);
// The variable will contain the expression.
const varValue = declaration.node;
const variable = createVariable(t, varName, varValue);
bodyStatementPath.replaceWith(variable);
// The variable will be the function's return value.
// > return export_default;
defaultExportExpression = varName;
} else {
// console.log("expression, last in body");
// > export default <expression>;
// convert to:
// > return <expression>;
bodyStatementPath.remove();
defaultExportExpression = declaration.node;
}
isModular = true;
} else if (t.isImportDeclaration(bodyStatementPath)) {
const {specifiers} = bodyStatementPath.node;
if (specifiers.length === 0) {
anonymousSources.push(bodyStatementPath.node.source);
} else if (
specifiers.length === 1 &&
specifiers[0].type === "ImportDefaultSpecifier"
) {
sources.push(bodyStatementPath.node.source);
vars.push(specifiers[0].local);
} else {
// Should not be supported this way, imo.
const importedID = bodyStatementPath.scope.generateUidIdentifier(
bodyStatementPath.node.source.value
);
sources.push(bodyStatementPath.node.source);
vars.push(importedID);
specifiers.forEach(({imported, local}) => {
namedImports.push(
t.variableDeclaration("var", [
t.variableDeclarator(
t.identifier(local.name),
t.identifier(importedID.name + "." + imported.name)
)
])
);
});
}
bodyStatementPath.remove();
isModular = true;
}
if (isLastBodyStatement && defaultExportExpression !== null) {
// Output the `return defaultExport;` statement.
// Done within the loop to have access to `bodyStatementPath`.
// Cannot insertAfter a removed node.
// Find the previous node which is not removed.
const returnStatement = t.returnStatement(
defaultExportExpression
);
const closestBeforePath = findClosestNonRemovedBefore(
bodyStatementPath,
i,
bodyPaths
);
if (closestBeforePath !== null) {
closestBeforePath.insertAfter(returnStatement);
} else {
programPath.unshiftContainer("body", [returnStatement]);
}
}
}
if (isModular) {
programPath.node.body = [
buildModule({
MODULE_NAME: moduleName,
IMPORT_PATHS: t.arrayExpression(
sources.concat(anonymousSources)
),
IMPORT_VARS: vars,
BODY: programPath.node.body,
NAMED_IMPORTS: namedImports
})
];
const isStrict = programPath.node.directives.some(
directive => directive.value.value === "use strict"
);
if (!isStrict) {
programPath.unshiftContainer(
"directives",
t.directive(t.directiveLiteral("use strict"))
);
}
}
}
}
}
};
};
function createDefaultExportVarName(scope) {
return scope.generateUidIdentifier("export_default");
}
function createVariable(t, id, valueExpression) {
return t.variableDeclaration("var", [
t.variableDeclarator(id, valueExpression)
]);
}
function findClosestNonRemovedBefore(path, pathIndex, siblingPaths) {
// Cannot insertAfter a removed node...
// Find the previous node which is not removed.
let refPath = path;
let i = pathIndex;
while (refPath.removed && i > 0) {
refPath = siblingPaths[--i];
}
return refPath.removed ? null : refPath;
}
...@@ -17,11 +17,7 @@ ...@@ -17,11 +17,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.3", "@babel/core": "^7.8.3",
"@babel/plugin-transform-modules-amd": "^7.8.3", "@babel/plugin-transform-react-jsx": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.8.3", "@babel/template": "^7.8.3"
"@babel/plugin-transform-react-jsx": "^7.8.3" }
},
"plugins": [
"@babel/plugin-transform-react-jsx"
]
} }
...@@ -46,6 +46,67 @@ NS.apply = function(a,b) { ...@@ -46,6 +46,67 @@ NS.apply = function(a,b) {
prop: true, prop: true,
on: true on: true
}; };
var fastDomEl = function(type, cfg) {
cfg = cfg || {};
var cls = cfg.cls || cfg['class'] || cfg.className,
style = cfg.style,
attr = cfg.attr || {},
prop = cfg.prop,
on = cfg.on || {},
renderTo = cfg.renderTo,
el;
el = document.createElementNS( svgNS, type );
el.setAttribute( 'xmlns', svgNS );
var i, _i, name;
for(i in cfg)
if( cfg.hasOwnProperty(i)){
name = i.toLowerCase();
if(name in used)
continue;
if(name.substr(0, 2) === 'on'){
on[ name.substr( 2 ) ] = cfg[ i ];
}else{
attr[i] = cfg[i];
}
}
if( cls ){
if(typeof cls === 'function'){
cls(setters.cls(el));
}else{
setters.cls(el)(cls);
}
}
for( i in attr ){
if(attr.hasOwnProperty( i )){
setters.attr( el, i )( attr[ i ] );
}
}
for( i in prop ){
prop.hasOwnProperty( i ) && ( el[ i ] = prop[ i ] );
}
for( i in on ){
on.hasOwnProperty( i ) && el.addEventListener( i, on[ i ] );
}
if( style ){
NS.apply( el.style, style );
}
for( i = 2, _i = arguments.length; i < _i; i++ ){
var child = arguments[ i ];
D.appendChild( el, child );
}
if( renderTo ){
D.appendChild( renderTo, el );
}
return el;
};
// ~jsx h function // ~jsx h function
var domEl = function( type, cfg ){ var domEl = function( type, cfg ){
...@@ -160,6 +221,7 @@ NS.apply = function(a,b) { ...@@ -160,6 +221,7 @@ NS.apply = function(a,b) {
return el; return el;
}; };
D.h = domEl; D.h = domEl;
D.s = fastDomEl;
D.removeChildren = function(el){ D.removeChildren = function(el){
var subEl; var subEl;
while((subEl = el.lastChild)){ while((subEl = el.lastChild)){
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
{ name: '.css', loader: cssLoader }, { name: '.css', loader: cssLoader },
{ name: '.jsx', loader: jsLoader }, { name: '.jsx', loader: jsLoader },
{ name: '.js', loader: jsLoader }, { name: '.js', loader: jsLoader },
{ name: '.svg', loader: jsLoader },
]; ];
var definitions = {}; var definitions = {};
...@@ -44,7 +45,7 @@ ...@@ -44,7 +45,7 @@
var definition = definitions[name]; var definition = definitions[name];
if(definition.notResolved === 0){ if(definition.notResolved === 0){
console.log(name,'execute') console.log(name,'execute')
definition.fn.apply(null, definition.deps.map(function(dep) { definition.exports = definition.fn.apply(null, definition.deps.map(function(dep) {
return dep === 'exports' ? definition.exports : definitions[dep].exports return dep === 'exports' ? definition.exports : definitions[dep].exports
})); }));
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<script> <script>
define('start', ['main.jsx'], function(main) { define('start', ['main.jsx'], function(main) {
main.default(); main();
}); });
</script> </script>
</body> </body>
......
import '../../cmp/field/LabeledField.jsx' import '../../cmp/field/LabeledField.jsx';
import Logo from '../../../svg/logo_vkusvill.svg';
export default D.declare('view.page.Login', ()=> export default D.declare('view.page.Login', ()=>
<div> <div>
<div class="big-logo"> <div class="big-logo">
<img src="uploads/images/svg/logo_vkusvill.svg" alt="Логотип ВкусВилл"/> <Logo width="660" height="300"/>
</div> </div>
<view.cmp.field.LabeledField label={'Login'}/> <view.cmp.field.LabeledField label={'Login'}/>
<view.cmp.field.LabeledField label={'Password'}/> <view.cmp.field.LabeledField label={'Password'}/>
......
...@@ -161,7 +161,14 @@ ...@@ -161,7 +161,14 @@
"@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-jsx" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3"
"@babel/template@^7.8.3": "@babel/runtime@^7.6.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1"
integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/template@^7.6.0", "@babel/template@^7.8.3":
version "7.8.3" version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ== integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==
...@@ -329,6 +336,14 @@ babel-plugin-dynamic-import-node@^2.3.0: ...@@ -329,6 +336,14 @@ babel-plugin-dynamic-import-node@^2.3.0:
dependencies: dependencies:
object.assign "^4.1.0" object.assign "^4.1.0"
"babel-plugin-transform-es2015-modules-simple-amd@git+https://github.com/dcleao/babel-plugin-transform-es2015-modules-simple-amd.git":
version "0.3.0"
resolved "git+https://github.com/dcleao/babel-plugin-transform-es2015-modules-simple-amd.git#8025a44c37e4163a526ad3d3741830ad26ed2708"
dependencies:
"@babel/runtime" "^7.6.3"
"@babel/template" "^7.6.0"
better-log "^1.3.1"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
...@@ -341,6 +356,11 @@ bcrypt-pbkdf@^1.0.0: ...@@ -341,6 +356,11 @@ bcrypt-pbkdf@^1.0.0:
dependencies: dependencies:
tweetnacl "^0.14.3" tweetnacl "^0.14.3"
better-log@^1.3.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/better-log/-/better-log-1.3.3.tgz#219a1c0383a8f9c4416897eda3acf9b4d6990e8f"
integrity sha1-IZocA4Oo+cRBaJfto6z5tNaZDo8=
block-stream@*: block-stream@*:
version "0.0.9" version "0.0.9"
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
...@@ -1563,6 +1583,11 @@ redent@^1.0.0: ...@@ -1563,6 +1583,11 @@ redent@^1.0.0:
indent-string "^2.1.0" indent-string "^2.1.0"
strip-indent "^1.0.1" strip-indent "^1.0.1"
regenerator-runtime@^0.13.2:
version "0.13.3"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
repeating@^2.0.0: repeating@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment