Commit dea7baaa by Иван Кубота

Modern build.

JSX support. Live reloading
parent 425328e8
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
<head> <head>
<title>Category mapper</title> <title>Category mapper</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<script src="livest-reloading.js"></script>
<script src="js/releasable-observer.js"></script> <script src="js/releasable-observer.js"></script>
<script src="js/pcg-base.js"></script> <script src="js/pcg-base.js"></script>
...@@ -27,7 +28,7 @@ ...@@ -27,7 +28,7 @@
<script src="js/view/base.js"></script> <script src="js/view/base.js"></script>
<script src="js/view/cmp/base.js"></script> <script src="js/view/cmp/base.js"></script>
<script src="js/view/cmp/menu.js"></script> <script src="js/view/cmp/menu.jsx"></script>
<script src="js/view/cmp/switch.js"></script> <script src="js/view/cmp/switch.js"></script>
<script src="js/view/cmp/table.js"></script> <script src="js/view/cmp/table.js"></script>
<script src="js/view/cmp/tag.js"></script> <script src="js/view/cmp/tag.js"></script>
...@@ -48,7 +49,7 @@ ...@@ -48,7 +49,7 @@
<script src="js/controller/quizBits/radioPhoto.js"></script> <script src="js/controller/quizBits/radioPhoto.js"></script>
<script src="main.js"></script> <script src="main.jsx"></script>
<link type="text/css" rel="stylesheet" href="main.css"> <link type="text/css" rel="stylesheet" href="main.css">
</head> </head>
<body> <body>
......
...@@ -37,7 +37,31 @@ var path = require('path'); ...@@ -37,7 +37,31 @@ var path = require('path');
var cache = {}; var cache = {};
var bCore = require( "@babel/core" ); var bCore = require( "@babel/core" );
var jsxServe = function(dir) {
var transformJSX = function(code, fileName, cb) {
bCore.transform(
code,
{
"plugins": [
[ "@babel/plugin-transform-react-jsx", {
"pragma": "D.h", // default pragma is React.createElement
"pragmaFrag": "D.f", // default is React.Fragment
"throwIfNamespace": false // defaults to true
} ]
],
sourceMaps: 'both',
sourceFileName: fileName
}, function( c, d, e ){
if(c){
cb(c);
}else{
cb(false, d);
}
} );
};
var transformServe = function(dir) {
return function (req, res, next) { return function (req, res, next) {
if (req.url in cache) { if (req.url in cache) {
return res.end(cache[req.url]) return res.end(cache[req.url])
...@@ -45,34 +69,23 @@ var jsxServe = function(dir) { ...@@ -45,34 +69,23 @@ var jsxServe = function(dir) {
/*if (req.url.substr(-8) === '.jsx.map') { /*if (req.url.substr(-8) === '.jsx.map') {
debugger debugger
}*/ }*/
if (req.url.substr(-4) === '.jsx') {
if (req.url.substr(-45 === '.scss') {
}else if (req.url.substr(-4) === '.jsx') {
fs.readFile(path.join(dir, req.url), function(err, data){ fs.readFile(path.join(dir, req.url), function(err, data){
debugger
if( err ){ if( err ){
next(); next();
}else{ }else{
bCore.transform( transformJSX(data+'', req.url, function(err, result) {
data+'', if(err){
{ res.end(err.message+'\n'+err.stack)
"plugins": [ }else{
[ "@babel/plugin-transform-react-jsx", { cache[ req.url + '.map' ] = JSON.stringify( result.map );
"pragma": "D.h", // default pragma is React.createElement res.set( 'SourceMap', req.url + '.map' );
"pragmaFrag": "D.f", // default is React.Fragment res.end( result.code )
"throwIfNamespace": false // defaults to true }
} ] })
],
sourceMaps: 'both',
sourceFileName: req.url
}, function( c, d, e ){
if(c){
res.end(c.message+'\n'+c.stack)
}else{
debugger
cache[ req.url + '.map' ] = JSON.stringify( d.map );
res.set( 'SourceMap', req.url + '.map' );
res.end( d.code )
}
} );
} }
}); });
...@@ -82,12 +95,65 @@ var jsxServe = function(dir) { ...@@ -82,12 +95,65 @@ var jsxServe = function(dir) {
} }
}; };
app.use(jsxServe('public')); app.use(transformServe('public'));
app.use(App.static('public')) app.use(App.static('public'))
app.use('/', function(req, res) { var lives = [];
res.end(print(JSON.stringify(data))); app.use('/', function(req, res, next) {
if(req.originalUrl === '/'){
res.end( print( JSON.stringify( data ) ) );
}else if(req.originalUrl === '/[live]'){
lives.push(res);
}
}); });
const util = require('util');
const readFile = util.promisify(fs.readFile);
const transformJSXPromised = util.promisify(transformJSX);
let debounce = {};
let shouldUpdate = false;
var doUpdate = async function(){
var files = [];
for( let filename in debounce ){
try{
var code = await readFile( filename ) + '';
var result = await transformJSXPromised( code, path.relative( './public', filename ) );
files.push( { file: '/'+path.relative( './public', filename ).replace(/\\/g, '/'), content: result.code } );
}catch( e ){
console.log( 'Error in ' + e );
}
delete debounce[filename];
}
var live, liveUpdate = JSON.stringify(files);
while((live = lives.pop()))
live.end(liveUpdate);
shouldUpdate = false;
};
let debounceUpdate = function(filename){
if(filename.indexOf('_tmp_')===-1 && filename.indexOf('_old__')===-1){
debounce[ filename ] = true;
if(!shouldUpdate){
shouldUpdate = true;
setTimeout(doUpdate, 300);
}
}
};
var watch = require('recursive-watch');
watch('./public', function(filename){
if(filename.indexOf('_tmp_')>-1)
return;
debounceUpdate(filename);
});
app.listen(APP_PORT); app.listen(APP_PORT);
console.log(`LISTEN port :`+APP_PORT); console.log(`LISTEN port :`+APP_PORT);
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
"csv-parser": "^2.3.2", "csv-parser": "^2.3.2",
"express": "^4.17.1", "express": "^4.17.1",
"node-async-router": "^0.0.2", "node-async-router": "^0.0.2",
"node-sass": "^4.13.1",
"recursive-watch": "^1.1.4",
"yargs": "^15.1.0" "yargs": "^15.1.0"
}, },
"scripts": { "scripts": {
......
...@@ -130,19 +130,3 @@ const quizGenerator = function(type, photo, subType) { ...@@ -130,19 +130,3 @@ const quizGenerator = function(type, photo, subType) {
} }
}; };
/*
К какому из продуктов относится это описание: "Важно знать"
В какой из продуктов входят данные ингредиенты: Ингредиент 1, Ингредиент 2, Ингредиент 3, Ингредиент 4
Выберите один ингредиент, который входит в продукт "Название продукта"
Какое из этих описаний относится к продукту "Название продукта"
Какие ингредиенты входят в продукт "Название продукта"
Какие ингредиенты НЕ входят в продукт "Название продукта"
В какие продукты входит следующий ингредиент "Название ингредиента"
Какой продукт изображен на картинке
Какое из этих описаний относится к продукту, изображенному на картинке
Выберите один ингредиент, который входит в продукт, изображенный на картинке
Какие ингредиенты входят в продукт, изображенный на картинке?
Какие ингредиенты НЕ входят в продукт, изображенный на картинке
*/
\ No newline at end of file
(function(PCG){ (function(PCG, glob){
var svgNS = 'http://www.w3.org/2000/svg'; var svgNS = 'http://www.w3.org/2000/svg';
// I am too lazy to do DOM manually / anyway this solution is optimal enough // I am too lazy to do DOM manually / anyway this solution is optimal enough
...@@ -27,12 +27,16 @@ ...@@ -27,12 +27,16 @@
var used = { var used = {
cls: true, className: true, 'class': true, cls: true, className: true, 'class': true,
attr: true, attr: true, style: true, renderTo: true,
prop: true, prop: true,
on: true on: true
}; };
// ~jsx h function // ~jsx h function
var domEl = function( type, cfg ){ var domEl = function( type, cfg ){
if(typeof type === 'function'){
return type(cfg);
}
cfg = cfg || {}; cfg = cfg || {};
var cls = cfg.cls || cfg['class'] || cfg.className, var cls = cfg.cls || cfg['class'] || cfg.className,
style = cfg.style, style = cfg.style,
...@@ -209,5 +213,60 @@ ...@@ -209,5 +213,60 @@
}; };
D.escapeCls = function(cls) { D.escapeCls = function(cls) {
return (cls+'').replace(/[^a-zA-Z0-9\-_]/g,''); return (cls+'').replace(/[^a-zA-Z0-9\-_]/g,'');
};
var _construct = function(ctor, cfg) {
//if it is not an arrow function
if('prototype' in ctor && ctor.prototype.constructor === ctor){
var cls = new ctor(cfg);
return 'dom' in cls ? cls.dom : cls;
}else{
return ctor(cfg);
}
};
var usage = {};
var populate = function(name, construct) {
var tokens = name.split('.'),
last = tokens.pop(),
first = tokens.shift();
// ES 6 consts are not in global scope. So we can not just add vars to window
var pointer = first?
new Function('g', 'return typeof '+first+' !== "undefined"?'+first+':(glob["'+first+'"] = {})')(glob)
:glob;
tokens.reduce(function(pointer, token, i, full) {
return pointer[token] || (pointer[token] = {});
}, pointer)[last] = construct;
return construct;
};
D.declare = function(name, ctor) {
var uses;
if(name in usage){
console.log(`${name} declaration updated. Usage count: ${usage[name].instances.length}`)
usage[ name ].ctor = ctor;
uses = usage[ name ].instances;
for( var i = 0, _i = uses.length; i < _i; i++ ){
var u = uses[ i ], d = u.dom;
u.dom = _construct(ctor, u.cfg);
if(d.parentNode){
d.parentNode.replaceChild( u.dom, d )
}
}
}else{
console.log(`${name} declared`)
uses = (usage[ name ] = {ctor: ctor, instances: []}).instances;
}
return populate(name, function construct (cfg) {
var dom = _construct(ctor, cfg);
uses.push({dom: dom, cfg: cfg});
return dom;
});
} }
})(window['PCG']); })(window['PCG'], typeof window !== "undefined" ? window :
\ No newline at end of file typeof WorkerGlobalScope !== "undefined" ? self :
typeof global !== "undefined" ? global :
typeof GLOBAL !== "undefined" ? GLOBAL :
Function("return this;")());
\ No newline at end of file
view.cmp.Menu = ({title, id, key, cls}) =>
div({
cls: _ => store.equal(key, id,
active => _( 'main-menu-item', cls,
'main-menu-item__'+D.escapeCls(id),
{'main-menu-item__active':active}
)),
onclick: ()=> store.set(key, id)
}, title);
\ No newline at end of file
D.declare('view.cmp.Menu',({title, id, key, cls}) =>
<div cls={_ => store.equal(key, id,active => _( 'main-menu-item', cls,
'main-menu-item__'+D.escapeCls(id),
{'main-menu-item__active': active}
))}
onclick={()=> store.set(key, id)}>{title}</div>
);
\ No newline at end of file
view.cmp.Tag = function(text) { D.declare('view.cmp.Tag', function(text) {
return D.span({cls: 'tag'}, '#', text) return D.span({cls: 'tag'}, '#', text)
}; });
\ No newline at end of file \ No newline at end of file
export const f = 33;
view.page.Export = function(){ //import f from './exp.js';
//console.log(f)
D.declare('view.page.Export', function(){
return <div cls="export-panel"> return <div cls="export-panel">
<div cls="title-gradient"> <div cls="title-gradient">
<div cls="export-sub-menu"> <div cls="export-sub-menu">
<div cls="export-sub-menu"> <div cls={_=>_("export-sub-menu", 'olo')}>
{view.cmp.Menu( { title: 'Тэги', id: 'tags', key: 'exportData' } )} <view.cmp.Menu title='Тэги' id='tags' key='exportData'/>
{view.cmp.Menu( { title: 'Связи', id: 'connections', key: 'exportData' } )} <view.cmp.Menu title='Связи' id='connections' key='exportData'/>
</div> </div>
<div cls="export-sub-menu"> <div cls="export-sub-menu">
...@@ -15,9 +18,10 @@ view.page.Export = function(){ ...@@ -15,9 +18,10 @@ view.page.Export = function(){
</div> </div>
</div> </div>
</div> </div>
{exportLogic( D.textarea( { cls: 'export-data' } ) )} {exportLogic( <textarea cls='export-data'/>)}
<div cls="export-comment">Connetction type{'<Enum>'}: 0 — продукт, 1 — компонент`</div> <div cls="export-comment">Connetction type{'<Enum>'}: 0 — продукт, 1 — компонент`</div>
<button onclick={e => console.log( e )}>btn</button> <button onclick={e => console.log( e )}>btn1</button>
</div> </div>
});
}
\ No newline at end of file
async function subscribe() {
let response = {};
try{
response = await fetch( "/[live]" );
}catch( e ){
}
if (response.status == 502) {
} else if (response.status != 200) {
// An error - let's show it
//debugger
//showMessage(response.statusText);
// Reconnect in one second
} else {
// Get and show the message
try{
let message = await response.text();
let data = JSON.parse( message ),
should = data.filter(live =>
[...document.scripts]
.map((a)=>a.src.replace(location.protocol+'//'+location.host,''))
.indexOf(live.file)>-1
).forEach(a=>{
try{
eval( a.content )
console.log('Reloaded: '+a.file);
}catch(e){
}
});
}catch(e){
}
//showMessage(message);
// Call subscribe() again to get the next message
}
setTimeout(subscribe, 1000);
}
subscribe();
\ No newline at end of file
data = null; data = null;
const init = function() { const init = function() {
let let
tagField, exportEl; tagField, exportEl;
div({ div({
renderTo: document.body, renderTo: document.body,
cls: 'content' cls: 'content'
}, },
div({ div({
cls: 'main-menu' cls: 'main-menu'
}, },
view.cmp.Menu({title: 'Продукты', id: 'products', key: 'mainMenuActive'}), view.cmp.Menu({title: 'Продукты', id: 'products', key: 'mainMenuActive'}),
view.cmp.Menu({title: 'Ингредиенты', id: 'components', key: 'mainMenuActive'}), view.cmp.Menu({title: 'Ингредиенты', id: 'components', key: 'mainMenuActive'}),
view.cmp.Menu({title: 'Генерируем', id: 'generate', key: 'mainMenuActive'}), view.cmp.Menu({title: 'Генерируем', id: 'generate', key: 'mainMenuActive'}),
view.cmp.Menu({title: 'Export', id: 'export', key: 'mainMenuActive', cls: 'menu-item-export'}), view.cmp.Menu({title: 'Export', id: 'export', key: 'mainMenuActive', cls: 'menu-item-export'}),
), ),
view.cmp.Switch({cls: 'content-area', key: 'mainMenuActive'}, { view.cmp.Switch({cls: 'content-area', key: 'mainMenuActive'}, {
products: new view.page.Products(), products: new view.page.Products(),
components: new view.page.Components(), components: new view.page.Components(),
'export': new view.page.Export(), 'export': <view.page.Export/>,
generate: new view.page.Generate() generate: new view.page.Generate()
}) })
); );
console.log(view.page.Export)
}; };
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