Git Product home page Git Product logo

magix-combine's People

Contributors

xinglie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

magix-combine's Issues

支持类mustach的模板语法

类underscore的模板语法没有学习成本,但因为模板标签内即是js的语法,这样会造成滥用,比如使用swtich、do while及声明函数然后再调用。我们希望模板语法是js的子集,提供有限的命令即可。目前类mustach的模板引擎比较多,我们就采用它的形式

调研过的artTemplate https://github.com/aui/art-templatecrox https://thx.github.io/crox/

暂定语法如下

输出语句

{{= variable }} 转义输出,同原来的<%= variable %>
{{! variable }} 直接输出,同原来的<%! variable %
{{@ variable }} 在渲染组件时传递数据,同原来的<%@ variable %>
{{: variable }} 数据绑定表达式,同原来的<%: variable %>

判断语句

if语句

{{if user.age > 20}}
    <span>{{= user.name }}</span>
{{/if}}

if else语句

{{if user.age > 20}}
    <span>{{= user.name }}</span>
{{else if user.age < 10}}
    <strong>{{= user.name }}</strong>
{{/if}}

循环语句

循环数组

{{each list as value index}}
    {{= index }}:{{= value }}
{{/each}}
<!-- index 可以省略 -->

解构对象赋值

{{each list as {id,name,age,gender} index}}
    {{= index }}:{{= id }} {{= name }} {{= age }} {{= gender }}
{{/each}}
<!-- index 可以省略 -->

解构数组赋值

{{each list as [,,id,,,name] index}}
    {{= index }}:{{= id }} {{= name }}
{{/each}}
<!-- index 可以省略 -->

循环对象

{{forin list as value key}}
    {{= key }}:{{= value }}
{{/forin}}
<!-- key 可以省略 -->

循环对象也可以解构赋值,参考上面的each语句

通用循环

{{for(let i=0;i<100;i+=2)}}
    {{= i }}
{{/for}}

可以理解为原生for循环

方法调用

{{= fn(variable1,variable2) }}

变量声明

{{ let a=user.name,b=30;c={} }}

不要使用函数声明,函数声明请统一在代码中定义,在模板中使用

已知问题

因分界符使用{{}} 像变量声明写成这样:{{let a={}}}将无法正确解析。

解决办法是在所有{{后面及}}前面加上空格,如写成{{ let a={} }}

其它问题请在下面留言讨论

支持类quickapp形式的模板

开发人员是首先服务好的客户

经过长时间的努力,magix终于把模板统一为类underscore方式的模板,不管类underscore这个模板好与不好,至少我们把之前杂乱的模板引擎给归一化。

underscore模板灵活,阅读也略有不便,从magix提供方来讲,也更不容易控制,比如用户写的某个控制命令,因为是原生的js写法,因此想统一做一些调整很难。这时候类mustache的模板就出现了 支持类mustache的模板语法

性能是第一要素

模板统一后,magix把局部更新从原来的 字符串拆分 转成了社区流行的dom diff

因为要兼容早期的类underscore模板及类mustache模板,虚拟dom的转换只能在运行时做。为了达到性能最优,能离线编译成方法调用,运行后得到虚拟dom,我们只能换模板引擎,首先支持使用jsx去写magix

后来又看到了快应用https://doc.quickapp.cn/tutorial/framework/framework-instructions.html,本质上比较重要的只有forif两个指令,既然事情并不复杂,那我们把类似快应用这样的语法也支持得了

html实体编码遇上js代码

单双引号

在js代码中

在js中单、双引号引起来的是字符串,如果我们要在字符串中使用单、双引号,需要反斜杠进行转义

let str='user\'s name';
// or
let str=" user's name";
// or
let str="she said:\"...\".";

如果在字符串中输出反斜杠,仍然是用反斜杠转义,即2个反斜杠输出1个反斜杠

在html代码中

html标签中,属性值通常用双引号引起来,也可以使用单引号或不用引号。

<input name=user />
<input name="user" />
<input name='user' />

这3种写法都正确,不过通常我们是选择用双引号引起来。
如果我们要在属性值中使用单、双绰号,我们不能直接写成下面这样

<input name=user'name />
<input name="user"name" />
<input name='user'name' />

这些全部是错误的。我们要像在js中对单、双引号转义一样,对属性中的单、双引号转义

在html中输出预留符号,可以使用字符实体转义的形式,这里有简单介绍:http://www.w3school.com.cn/html/html_entities.asp。即想输出一个双引号可以使用&quot;的形式,

<input name="user&quot;name" />

除此之外,html还支持十进制与十六进制编码的形式输出字符,如我们知道字符aascii码的十进制是97 十六进制是61
所以我们在页面body中输出一个字符a,有以下3种形式

<body>
  a<!--直接输出-->
  &#97;<!--十进制输出-->
  &#x61;<!--十六进制输出-->
</body>

同样,单双引号也有十进制(单:39,双:34)与十六进制(单:27,双:22),所以我们在属性中输出一个单引号有2种选择,十进制与十六进制

<input name='user&#39;name' /><!--十进制-->
<input name='user&#x27;name' /><!--十六进制-->

而输出一个双引号则有3种选择

<input name="user&quot;name" /><!--实体-->
<input name="user&#34;name" /><!--十进制-->
<input name="user&#x22;name" /><!--十六进制-->

当js代码遇上实体编码

我们可以通过dom节点提供的事件写上调用js的代码,如点击body弹出hello这个字符串,我们可以写成

<body onclick="alert('hello')">
click here
</body>

如果我们的需求是就弹出一个双引号呢?
根据前述规则,我们要写成:

<body onclick="alert('&quot;')"><!--这里用十进制或十六进制都可以-->
click here
</body>

当然,alert里的单引号也可以使用十进制或十六进制编码

<body onclick="alert(&#34;&#39;&#34;)"><!--&#34;单引号  &#39;双引号-->
click here
</body>

这样也是可以的。
是不是有点xss的感觉😂

如果我们把弹双引号的需求改成单引号呢?

<body onclick="alert(''')"><!--这样html中是合法的,但js中并不合法,因为在js中,中间的单引号并没有转义-->
click here
</body>

如果我们用十进制或十六进制编码呢?

<body onclick="alert('&#34;')"><!--这样可以吗-->
click here
</body>

这样仍然是不可以的
我们要对js字符串中的单引号进行转义,如

<body onclick="alert('\'')"><!--转义后可正确弹出-->
click here
</body>

<body onclick="alert('\&#34;')"><!--转义后可正确弹出-->
click here
</body>

前面的onclick="alert('\'')"看起来还正常,后面的这个onclick="alert('\&#34;')"就有点不直观了。因为后面这个看上去反斜杠像在转义&这1个字符,而&在js的字符串中并不需要转义的。

动态输出

如前述的alert弹出的消息,如果是一个变量控制,动态输出呢?

<body onclick="alert('${msg}')">
click here
</body>

那我们这个msg字符串就得注意了,从这个示例来看,这个动态的msg即出现在属性onclick中,也出现在alert的单引号开始的字符串中。

我们要对msg中的双引号转成&quot;&#34;&#x22;,并对msg中单引号的前面加上一个反斜杠\ 😭

题外话:对msg中的反斜杠需要做double处理,因为反斜杠在html属性中并不是特殊的,但在js的字符串中是特殊的。因此正确的做法是对反斜杠及单引号前面各加上一个反斜杠

然而,你并不能保证属性是用双引号,alert中的字符串用的是单引号,因为可以写成下面这样

<body onclick='alert("${msg}")'>
click here
</body>

😂

这种情况我们要对msg中的单引号转成&#39&#x27,并对msg中双引号前面加上一个反斜杠\

题外话:同上

看上去要根据不同的情况做不同的处理,其实也不需要
我们只需要对单、双引号前面加上一个反斜杠\然后再对单、双引号实体编码即可。

在js中如果反斜杠后面跟的不需要反斜杠转义的字符,那么这个反斜杠是被丢弃的,因此像

var str="user\'s name";

单引号前面多加一个反斜杠也不要紧的。

自动化处理与识别提醒

在magix项目中,由于magix-combine的支持,可识别出属性中js代码的部分,并自动化处理,如

<button mx-click="showName({name:'<%=name%>'})">click here</button>

name这个变量可包含任意的单、双引号及反斜杠。工具自动识别并处理,开发者不需要做任何事情。

而对于这样的写法:

<button mx-click="showName({name:'&#34;'})">click here</button>
<!-- or-->
<button mx-click="showName({name:'\&#34;'})">click here</button>

第一种写法其实并不正确,但第二种情况看上去又怪怪的。magix-combine工具能识别出来是否需要添加反斜杠,并自动添加处理。
第一种需要添加反斜杠,工具会自动加上,并提醒开发者这里的写法是不正确的。
第二种说明开发者意识到了问题所在,自己处理了,工具就不再处理也不再提醒开发者。

#开头的文件处理命令

js文件做为代码片断

在开发阶段,我们可以把文件拆分的很碎,来更好的分配给不同的开发者开发。
拆分的这些个零碎的文件在打包时可以合并成一个文件,减少动态加载时的http请求。

即某几个js文件只是开发时的一些代码片断,打包合并时可以合到别的js文件中。

//#snippet

表明一个js文件是代码片断文件,不需要做为单独文件打包,可以使用//#snippet指令,如

// app/views/menu.js
//#snippet;
var menus=['1','2','3'];

打包后,在build文件夹下并不会输出app/views/menu这个文件。

'@../jsfile.js'

当我们写好js代码片断文件后,想让这个片断出现在另外一个js文件合适的位置,可以使用'@../jsfile.js'这个@指令,如

// app/views/default
'@app/views/menu.js';
var magix=rquire('magix');
console.log(menus);

跳过编译流程中的一些步骤

我们在打包编译一个js文件时,在整个流程上会有打包开始前->加入loader(define)->处理样式->处理模板->处理js代码片断->打包结束。在这个流程上某一步不想参与时,可以使用'#exclude'指令

//#exclude

排除某些打包流程中的项目,如不参与第三方代码的编译及添加define可以这样写

// app/views/menu.js
//#snippet;
//#exclude=define,beforeProcessor // => //#exclude=loader,before
var menus=['1','2','3'];

Trying to get in touch regarding a security issue

Hey there!

I'd like to report a security issue but cannot find contact instructions on your repository.

If not a hassle, might you kindly add a SECURITY.md file with an email, or another contact method? GitHub recommends this best practice to ensure security issues are responsibly disclosed, and it would serve as a simple instruction for security researchers in the future.

Thank you for your consideration, and I look forward to hearing from you!

(cc @huntr-helper)

去除文件中注释的正则

http://blog.csdn.net/justoneroad/article/details/8227254

var reg = /("([^\\"](.)?)")|('([^\\'](.)?)')|(/{2,}.?(\r|\n))|(/_(\n|.)?_/)/g,// 正则表达式
str = $('event').html(); // 欲处理的文本
console.log(str); // 打印出:原文本
console.log(str.match(reg));// 打印出:匹配子串
str.replace(reg, function(word) { // 去除注释后的文本
return /^/{2,}/.test(word) || /^/*/.test(word) ? "" : word;
});

css中,高清屏下,图片适配怎么做?

media query或image-set等并不是所有的浏览器都支持,而且要多写很多样式,能不能就用基础的样式规则进行适配呢?

动态设置图片的url

如果我们能动态的设置样式规则中图片的url,那么我们很容易做到根据不同的屏幕使用不同的图片,而样式本身并没有这个能力。
在magix项目中,通过magix-combine这个工具把样式文件内容合并到js文件中,在js文件中,样式内容仅仅是一个长字符串,因此我们在把样式内容合并到js中时,对这个字符串进行修改,使用js的能力达到动态设置图片的url的需求

示例

/*app/views/index.css*/
.test{
    background:url(a.jpg);
}
// app/views/index.js
var Magix = require('magix');
Magix.applyStyle('@index.css');

我们在配置magix-combine编译选项中,添加一个cssUrlMatched回调,如

combineTool.config({
    compressCss: false,
    tmplFolder: tmplFolder,
    srcFolder: srcFolder,
    loaderType: 'amd',
    cssUrlMatched(url) {
        if (/\.jpg$/.test(url)) {
            return '"+Magix.adapt("' + url + '")+"';
        }
        return url;
    }
});

则最终打包出来的app/views/index.js中,样式这块的代码如下

var Magix = require('magix');
Magix.applyStyle("080-_app_views_index_",".080-_app_views_index_test {\n  background: url("+Magix.adapt("a.jpg")+")\n}") 

我们把样式中加载图片的地方,换成使用js动态加载图片,这样在js的方法中我们可以根据不同的情况返回不同的图片即可。
接下来只要实现一个adapt方法并挂到Magix对象上即可

配置说明

该说明无法保证及时更新,推荐使用Visual Studio Code或阅读d.ts文件:https://github.com/thx/magix-combine/blob/master/index.d.ts

配置参数说明

tmplFolder

string 包含html,css,js的模板目录,默认 ./tmpl

srcFolder

string 把html,css,js合并后的目录,默认 ./src

md5CssFileLen

number 生成样式文件选择器时,中间文件的长度,默认 2

md5CssSelectorLen

number 生成样式文件选择器时,选择器的长度,默认 2

cssnanoOptions

object css压缩选项,更多信息请参考https://www.npmjs.com/package/cssnano 默认 {safe:true}

lessOptions

object less压缩选项,更多信息请参考https://www.npmjs.com/package/less

sassOptions

object sass压缩选项,更多信息请参考https://www.npmjs.com/package/node-sass

cssSelectorPrefix

string css选择器前缀,通常可以是项目的简写,默认mx-

loaderType

string 加载器类型,默认 cmd ,支持[amd,cmd,iife,kissy,webpack]

htmlminifierOptions

object html压缩选项,更多信息请参考https://www.npmjs.com/package/html-minifier

log

boolean 是否输出普通日志,默认 true

logCssChecker

boolean 是否输出css检测,默认 true

compressCss

boolean 是否压缩css,有时候项目复杂时,压缩css较费时,可以在开发时先不压缩,上线时再压缩。默认true

compressCssSelectorNames

boolean 是否压缩样式文件中的类选择器名称,默认false

addEventPrefix

boolean 是否在mx-event事件上添加前缀占位符,以提升事件处理时的性能。默认 true。magix3.x版本必须开启

bindEvents

array 绑定表达式<%:expr%>绑定的事件。默认 ['change']

bindName

string 绑定表达式<%:expr%>绑定的处理名称,默认 s\u0011e\u0011t

globalCss

array 全局样式,不推荐使用,默认 []

scopedCss

array 全局但做为scoped使用的样式,默认 []

useAtPathConverter

boolean 是否使用@路径转换功能,默认 true

compileFileExtNames

array 工具在编译文件时,编译的文件后缀名,默认 ['js','mx']

tmplUnchangableVars

object 模板中值不会变化的变量,用于提升子模板分析时的效率,默认 {}

tmplGlobalVars

object 模板中全局变量

outputTmplWithEvents

boolean 是否输出模板中所有的事件数组,为brix提供,默认 false

disableMagixUpdater

boolean 是否禁用magix内置的updater,默认false,该项决定模板如何输出

tmplPadCallArguments

function(name) 模板中某些函数的调用,我们可以动态添加一些参数。
name 调用的函数名

compileBeforeProcessor

function(content) 开始编译某个js文件之前的处理器,可以加入一些处理,比如typescript的预处理
content 文件内容

compileAfterProcessor

function(content) 结束编译
content 文件内容

afterDependenceAnalysisProcessor

function(e) 分析完依赖后的处理器,可以在这个地方加入一些其它流程。
e 编译信息

mxTagProcessor

function(tmpl,e) mx-tag的处理器
tmpl 待处理的模板
e 编译信息

cssNamesProcessor

function(tmpl,cssNamesMap) 模板中名称的处理器
tmpl 待处理的模板
cssNamesMap 选择器映射对象

compressTmplCommand

function(tmpl) 压缩模板命令,扩展用
tmpl 待处理的模板

cssUrlMatched

function(url) 样式中匹配到url时的处理器

tmplImgSrcMatched

function(url) 模板中img匹配到url时的处理器

resolveModuleId

function(id) 处理模块id时的处理器
id 模块id

resolveRequire

function(reqInfo,e) 处理rqeuire时的处理器
reqInfo require语句对应的信息
e 编译信息

关于style的scope

如果想让style只在当前范围内生效,现在有了scope属性,不过浏览器支持的并不好。

之前我们一直用命名空间的方式来做:

.a .title{color:red}
<div class="a">
   <span class="title">header</span>
</div>
.b .title{color:green}
<div class="b">
   <span class="title">header</span>
</div>

看上去并没有问题,我们也一直这样用,不过考虑以下问题:

  1. 基于加载的时间问题,我们想把css加载变成动态的
  2. magix view是嵌套的,a b之间可能任意嵌套

假设我们访问 /a 页面,它上面只有一个a模块,因为是动态加载,此时页面是这样的:

<head>
  <style>
    .a .title{color:red}
  </style>
</head>
<body>
  <div class="a">
   <span class="title">header</span>
  </div>
</body>

此时没有问题,如果我们再访问 /ab 页面,它上面是a b 两个模块, 同时a嵌套在b里:

<head>
  <style>
    .a .title{color:red}
  </style>
  <style><!--单页应用,a之前已经加载过,所以到这个页面我们只需要加载b的样式即可-->
    .b .title{color:green}
  </style>
</head>
<body>
  <div class="b">
   <span class="title">header</span>
   <div class="a">
     <span class="title">header</span>
    </div>
  </div>
</body>

你会发现内部的header并不是红色而是绿色,这是css权重的问题,具体细节可以咨询 @yisibl

命名空间方案在magix项目中存在一定问题,而追踪复现该问题非常不易,通常发现了问题,你刷新下页面它就变好了,只有在特定的跳转顺序(跳转顺序决定了加载顺序),出现了加载顺序问题才能复现。

解决该问题的最好方案是style的scope

回头再说scope,虽然浏览器支持不好,不过我们有插件来做,这种插件也非常多。

但是我们还要考虑性能的问题,即能离线的不要在线上计算。所以插件我们不选择

上述问题的另外一个解法是把css中的类名写长一些,比如统一加上模块名之类的。

如:

.a-title{color:red}
.b-title{color:green}

这样也不会有问题,唯一要做的就是不能忘记加前缀,且不能重复,需要开发者保证唯一。

而magix-combine中有一个这样的功能,自动加前缀,防止整个项目中重复的功能,即把这种工作交给工具来做。

拆分模板与子模板的打包过程

原始方式:

{
   tmpl:'@index.html'
}
//=>
{
   tmpl:'xxx',
   tmplData:{}
}

改为现在的

{
   tmpl:'@index.html',
   tData:'@index.html:data'
}
//=>
{
   tmpl:'xxx',
   tData:{}
}

这样一个js文件可以对应多个模板文件,多个模板文件可以各自局部刷新

自定义标签与组件库

引子

magix3支持任意节点作为渲染的容器,写上mx-view属性就可以在节点内渲染出view的内容

比如如下示例

<div mx-view="path/to/view"></div>

<ul mx-view="path/to/view"></div>

<table><tr mx-view="path/to/view"></tr></table>

当我们需要传参数时

<div mx-view="path/to/view?p1=a&ap2=b"></div>

<ul mx-view="path/to/view?p1=a&p2=b"></div>

<table><tr mx-view="path/to/view?p1=a&p2=b"></tr></table>

这是最基础的使用方式,接下来所有的改进写法均由magix-combine这个工具编译成如上形式

改进参数传递

最初magix的vframe是参考iframe进行设计的,mx-view对应着iframe的src属性,所以参数传递设计成url的方式

当我们参数较多时,这个类url方式的传递在阅读时相当不友好,所以我进行了第一次的参数改进如下

<div mx-view="path/to/view"
    view-p1="a"
    view-p2="b">
<div>

在节点属性里,以view-开头的表示传递给view的参数。在标签内,可以任意换行排版,因此可以解决前面较多参数时,不便阅读的问题

统一渲染标签

最初magix的vframe是参考iframe进行设计的,iframe对应着<iframe>标签,而vframe则对应着<vframe>标签。但后来在实施的过程中,因为自定义标签在ie下的问题,及标签嵌套的问题,如ul标签做为外层的节点,li做为子view的节点:<ul><vframe mx-view="path/to/view"><li>aaa</li></vframe></ul> 这种结构显然是不合适的,后来就放弃使用vframe标签了

虽然vframe标签有一些问题,但是在我们review代码的时候,可以快速浏览vframe标签来确定当前view使用了哪些子view

magix-combine离线编译的出现,我们可以使用vframe标签,并在编译的时候把vframe标签编译成相应的原生标签

<mx-vframe src="path/to/view" />

默认使用div标签,对于需要指定tag的情况下,我们可以使用tag属性,如

<table>
    <mx-vframe src="path/to/view" tag="tr" />
</table>

同时在这种情况下,传递参数的形式不强制以view开头,如

<table>
    <mx-vframe src="path/to/view" tag="tr" list="[1,2,3]" />
</table>

因为list并不是标签tr的原生属性,所以list会当成参数传递给path/to/view,当传递的参数名称和原生属性一样时(冲突),才需要使用view的前缀,如

<table>
    <mx-vframe src="path/to/view" tag="tr" view-id="tr" />
</table>

扩充mx-tag标签

前面我们使用了自定义标签mx-vframe来处理view的加载,同时对于代码片断的引用,我又定义了mx-include标签把其它文件的内容放在标签所在的位置,如

<mx-include src="../snippets/footer.html" />

所以目前我们有2个以mx开头的标签mx-vframemx-include来处理view的加载及代码片断的复用

再后来我们有了magix-gallery组件仓库

当前其它前端框架多以自定义标签来处理组件,使用自定义标签的方式更语义化,同时代码也更简练

magix-combine也支持自定义标签引用组件,如

<mx-slider.range  value="19,20" />

表示使用mx-slider组件中的range组件,上面的代码会被magix-combine编译成

<div mx-view="app/gallery/mx-slider/range?value=%5B19%2C20%5D"></div>

使用自定义标签的形式不需要提供view的路径,因为已经包含在自定义标签信息里了,这个路径信息配置在magix-combine的galleries配置项里

自定义组件库

如前所述,我们可以使用以mx开头的自定义标签来方便的使用magix提供的组件,显然,magix提供的组件是有限的,如果自己也想建组件库,并方便的使用,需要做如下的配置。

确定前缀

像magix提供的组件,前缀是mx,如<mx-calendar.datepicker />
比如你可以使用其它的简洁前缀如<pft-user.card />

确定存放位置

确定好前缀pft后就要确定你的组件放在项目中哪个位置,你可以任意放,比如放在src/app/pfts/文件夹里

然后可以参考magix-gallery开发自己的组件即可

同时要告诉magix-combine新增的前缀,如

let combineTool=require('magix-combine');

combineTool.config({
    galleries:{
        pftRoot:'app/pfts/'
    }
});

其它配置

自定义标签默认使用div标签进行渲染,有时候我们希望使用如input标签渲染,我们可以配置对应的map信息来指定,如

let combineTool=require('magix-combine');

combineTool.config({
    galleries:{
        pftRoot:'app/pfts/',
        pftMap:{
            'pft-user.card':{
                tag:'input'
            }
        }
    }
});

这样配置完成后,项目中所有<pft-user.card />均会使用input标签渲染,有时候我们想只在特定的地方使用input标签,则可以写成 <pft-user.card tag="input"/> 在标签内使用tag属性指定标签即可

有时候我们希望更改默认的路径,我们可以使用path配置,如

let combineTool=require('magix-combine');

combineTool.config({
    galleries:{
        pftRoot:'app/pfts/',
        pftMap:{
            'pft-user.card':{
                path:'path/to/view',
                tag:'input'
            }
        }
    }
});

即使改变路径,也必须在pftRoot这个文件夹下!

对于更灵活的控制,我们可以使用processor配置项进行处理,如

let combineTool=require('magix-combine');

combineTool.config({
    galleries:{
        pftRoot:'app/pfts/',
        pftMap:{
            'pft-user.card':{
                 processor(tagInfo){
                     return `<div ${tagInfo.attrs}>${tagInfo.content}</div>`;
                 }
            }
        }
    }
});

合并动作为什么不放到打包上线时做?

我们有上线前打包的工具:gulp-magix-cmd gulp-magix-combine

但并不推荐在打包上线时才打包,为什么?

当项目复杂时,且开发人员各自写代码的方式并不能按约定产出一致的代码,那么仅依赖打包工具,有可能会出现没有正确的把html打到js里。是工具都有bug的可能。

我们在开发时能正确跑的程序,因为工具或开发者未按约定产出代码,导致合并后的代码有问题,并不能第一时间被发现,因为你不可能在复杂的项目里,打包后,每个页面每个点都过一遍。

当我们把这个过程提前,变成在开发的时候合并,上线时仅做压缩,这样就不会有任何问题了

不推荐在项目中使用全局样式

在magix项目中,我们简单的把样式划分为2类

全局样式

包括像clearfixmt20等通用类,也包括项目中通用类

局部样式

仅当前view使用的样式

如果一些样式仅某几个view使用,建议这种样式要么归类到全局样式中,要么归类到局部样式,即使用到的这几个view都加载这个样式,适当冗余下也没关系

局部样式在开发中是没问题的,这些样式就是给当前view使用的,我们来讨论下全局样式

全局样式只要知道类名,我们可以在模板中、js代码中进行使用,看上去也挺方便的,不过在我看来有以下几个问题

  1. 开发中无法快速定位全局样式在哪个文件中(我们通常会把全局样式也分类到几个文件中去)
  2. 上线时无法压缩(有时候为了防止冲突,项目中有些全局样式名称起的非常长,如batch-operation-button-active),这种长名称应该像js变量一样被压缩才好
  3. 无法确定某些样式是否还被使用(通常维护项目的人只敢在全局样式中添加样式,不敢删除样式)
  4. magix-combine无法安全检测全局样式(全局样式可以在js中进行添加,magix-combine无法通过静态分析,识别出哪些全局样式被使用了)
  5. 容易与第三方的样式冲突(如果项目中想引用一些第三方的带样式的组件,很可能冲突,比如都定义了clearfix但实现不同)

虽然全局样式有一些问题,但是我们仍然是要用的啊,所以magix-combine通过另外一种方式来做这个"全局"样式

magix-combine工具中,有一项scopedCss,是对全局样式进行scoped的。

配置如下

combineTool.config({
    scopedCss: [
        './tmpl/app/snippets/cube-neat.css',
        './tmpl/app/snippets/app-normalize.less',
        './tmpl/app/snippets/app-layout.less',
        './tmpl/app/snippets/app-iconfont.less',
        './tmpl/app/snippets/app-loading.less',
        './tmpl/app/snippets/app-btn.less',
        './tmpl/app/snippets/app-form.less',
        './tmpl/app/snippets/app-dialog.less',
        './tmpl/app/snippets/app-table.less',
        './tmpl/app/snippets/app-util.less'
    ]
});

把项目中要进行全局使用的样式都配置进来。magix-combine会把这些样式合并并在scoped.style这个占位符中输出,如要对scopedCss这个配置项配置的样式进行输出,可以使用如下代码

let Magix = reqire('magix');
Magix.applyStyle('@scoped.style');//输出整个scopedCss中配置的样式

magix-combine会改写这些样式文件中的类名,如果未启用压缩样式选择器名称的功能,则选择器名称是以文件路径进行改写的,方便开发者快速定位样式所在文件。
同时会改写整个项目中模板里面使用的样式,如果要在js中使用样式,则要加上@scoped.style前缀,如

console.log('@scoped.style:btn');

这样magix-combine就会知道全局样式中哪些样式被使用,哪些未被使用。

而发布上线时,我们可以开启压缩选择器名称的功能,压缩后不管多长的选择器名称均被压短,即做到了让开发者随意命名,又不用担心过长的名称占用文件传输时间。

当我们引入第三方样式时,也不用担心冲突

@占位符使用

以下介绍的所有@占位符后面的文件,均可以用如'@../../x.html'相对路径

@filename.html

在当前位置输出filename.html的文件内容,以字符串的形式输出

假设目录结构如下

|____index-main.html
|____index-manager.html
|____index.js

index-main.html中的代码如下

<div>index-main</div>

index.js中的代码如下

var mainHTML='@index-main.html';
var managerHTML='@index-manager.html';

最终生成的index.js如下

var mainHTML='<div>index-main</div>';
var managerHTML='contents of index-manager.html';

@filename.html会被读取出的文件内容替换掉

当模板出现在Magix.View中时,如

var Magix=require('magix');
module.exports=Magix.View.extend({
    tmpl:'@index.html'
});

输出的将是一个对象,这有利于后续添加其它信息,所以只有这里是对象

@filename.[css,less,scss]

在当前位置输出filename.[css,less,scss]的内容,目前工具支持.css.less.scss三种样式文件。以字符串的形式输出,该占位符只能配合Magix.applyStyle方法使用!输出文件内容字符串时,会修改css中的类名,确保在当前项目唯一。

假设目录结构如下

|____index.html
|____index.css
|____index.js

index.css内容如下

.conent{
    color:red
}

index.html内容如下

<div class="content">
   ....
</div>

index.js内容如下

var Magix=require('magix');
var indexHTML='@index.html';
Magix.applyStyle('@index.css');

最终生成的index.js内容如下

var Magix=require('magix');
var indexHTML='<div class="mp-et5-content">...</div>';
Magix.applyStyle('mp-ec5','.mp-et5-content{color:red}');

names@filename.[css,less,scss]

输出文件filename.[css,less,scss]旧选择器与新选择之间的映射对象

假设目录结构如下

|____index.html
|____index.css
|____index.js

index.css内容如下

.conent{
    color:red
}
.title{
    color:green
}

index.js内容如下

var map='[email protected]';
console.log(map);

最终生成的index.js内容如下

var map={content:'mp-et5-content',title:'mp-et5-title'};
console.log(map);

@filename.[css,less,scss]:className

filename.[css,less,scss]文件经工具编译后的className输出在当前位置,以字符串的形式输出。

假设目录结构如下

|____index.html
|____index.css
|____index.js

index.css内容如下

.conent{
    color:red
}
.title{
    color:green
}

index.html内容如下

<div class="content">
   ....
</div>

index.js内容如下

var Magix=require('magix');
var indexHTML='@index.html';
Magix.applyStyle('@index.css');
var title='@index.css:title';
console.log(title);

最终生成的index.js内容如下

var Magix=require('magix');
var indexHTML='<div class="mp-et5-content">...</div>';
Magix.applyStyle('mp-ec5','.mp-et5-content{color:red}.mp-et5-title{color:green}');
var title='mp-et5-title';

console.log(title);

ref@filename.[css,less,scss]

引用filename.[css,less,scss]文件经工具编译后的类名,来修改当前js文件中使用的模板html中的class

假设目录结构如下

|____index.html
|____index.css
|____index.js
|____index-inner.html
|____index-inner.js

如果index-inner肯定是渲染在index中,我们让在index范围内生效的class也能影响到index-inner,需要在子view中显式指定引用到的样式(受哪个样式的影响)

index.css内容如下

.conent{
    color:red
}
.title{
    color:green
}

index.js中的内容如下

var Magix=require('magix');
Magix.applyStyle('@index.css');

index-inner.html中的内容如下

<div class="title">title</div>

index-inner.js中的内容如下

'[email protected]';//开头写上引用index.css
var innerHTML='@index-inner.html';

最终生成的index-inner.js内容如下

var innerHTML='<div class="mp-et5-title">title</div>';

因为index-inner肯定是渲染在index中,所以我们可以把样式统一在index中加载,子页面只需要引用父页面的样式,修改掉模板中的类名即可。
应用场景:某个复杂的view内部拆分,比如一个创意提交表单,内部根据创意类型拆分了n个子view,但是它们的样式可以统一写在最外部的view上,不必要每个子view写一份

@ moduleId

当前文件的模块id,如app/views/default

假如某个文件的位置为app/views/default

var Magix=require('magix');
module.exports=Magix.View.extend({
    render:fucntion(){
        console.log('@moduleId');
    }
});

最后生成的文件内容如下

define('app/views/default',['magix'],function(require,module){
    var Magix=require('magix');
    module.exports=Magix.View.extend({
        render:fucntion(){
            console.log('app/views/default');
        }
    });
});

gulp使用示例

package.json

{
    "name": "magix-test",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "dependencies": {
        "magix-combine": "",
        "gulp": "",
        "gulp-watch": "",
        "gulp-uglify": "",
        "del": ""
    }
}

gulpfile.js

var tmplFolder = 'tmpl'; //template folder
var srcFolder = 'src'; //source folder
var buildFolder='build';//

var gulp = require('gulp');
var watch = require('gulp-watch');
var fs = require('fs');
var combineTool = require('magix-combine');
var del = require('del');


combineTool.config({
    tmplFolder: tmplFolder,
    srcFolder: srcFolder,
    loaderType:'cmd' //or amd
});

gulp.task('cleanSrc', function() {
    return del(srcFolder);
});
gulp.task('combine', ['cleanSrc'], function() {
    return combineTool.combine();
});
gulp.task('watch', ['combine'], function() {
    watch(tmplFolder + '/**/*', function(e) {
        console.log(e.path);
        if (fs.existsSync(e.path)) {
            combineTool.processFile(e.path);
        } else {
            combineTool.removeFile(e.path);
        }
    });
});

var uglify = require('gulp-uglify');
gulp.task('cleanBuild', function() {
    return del(buildFolder);
});
gulp.task('build', ['cleanBuild'], function() {
    gulp.src(srcFolder + '/**/*.js')
        .pipe(uglify({
            compress: {
                drop_console: true,
                drop_debugger: true
            }
        }))
        .pipe(gulp.dest(buildFolder));
});

支持字符串内的css@命令替换

原来以左右字符串的引号做为边界,但使用时略不便,更新正则

/(['"]?)\(?(global|ref|names)?@([\w\.-]+?)(\.css|\.less|\.scss)(?:\[([\w-,]+)\]|:([\w\-]+))?\)?\1(;?)/g

替换js中的css名称

子模板拆分与变量声明存在的问题

考虑以下html模板

<div class="craft-content">
    <div id="sidebar">
        <% var i; %>
        <% for(i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
    <div class="article">
        <% for(i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
</div>

在节点sidebar中声明了变量i,在article节点里重复使用了变量i,这段代码magix-combine分析时存在这个问题:进行子模板拆分时,以节点为区块进行划分,而变量声明是跨节点的,会导致子模板拆分不正确。

子模板分析可以参考这个:#18
模板中的变量声明不能跨节点

如何避免这个问题?

  1. 变量用时才声明,模板中没必要集中声明变量,在需要的地方声明一下即可,可重复声明
  2. 如果集中声明就提取到公共父节点

如变量用时才声明:

<div class="craft-content">
    <div id="sidebar">
        <% for(var i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
    <div class="article">
        <% for(var i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
</div>

如变量提取到父节点声明

<div class="craft-content">
    <% var i; %>
    <div id="sidebar">
        <% for(i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
    <div class="article">
        <% for(i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
</div>

推荐用时再声明的方式

非常感谢@bigfengyu

支持从html中提取所需要的信息

语法

'@file.html:type'

提取子模板数据

'@file.html:data'

提取模板中mx-keys所有key

'@file.html:keys'

提取模板中mx-event事件

'@file.html:events'

支持单个文件的写法

参考vue的做法,magix计划增加.mx后缀的文件,该文件内

<template>
 tmpl string
</template>
<style>

</style>
<script>

</script>

来表示模板、样式、代码。

<mx-tag>来处理重复的代码片段及magix-gallery组件

当我们在处理html时,通常会遇到重复的,又稍微复杂的html片段,我们以一个loading为例

<div mx-view="app/views/list">
    <div class="loading">
        <span></span>
        <span></span>
        <span></span>
    </div>
</div>

比如我们用css3写3个圆点不断变换的动画。当项目中需要很多地方用这个loading动画时,我们需要把它复制粘贴到需要用到的地方。

如果后期视觉设计师对这个loading动画不满足,要换一种,万一要动html结构,那我们前端工程师就有得忙了。

magix-combine针对这个问题,有一个解决方案,如下:在html中定义标签,然后在magix-combine工具的配置中,统一处理。

<div mx-view="app/views/list">
    <mx-loading />
</div>

使用mx-开头的标签来定义一个magix-combine要识别处理的自定义标签

然后我们在magix-combine的配置里来处理这个自定义的标签

var combineTool = require('magix-combine');

combineTool.config({
    loaderType:'cmd', //or amd
    mxTagProcessor:function(tagInfo){
         if(tagInfo.tag=='loading'){//获取标签名字,去掉mx-后的名字
            return '<div class="loading"><span></span><span></span><span></span></div>';
         }
        return '';//如果不能识别,则返回空字符串,删除掉这个标签
    }
});

当然,这个自定义的mx-标签也可以写属性和内容,然后解析的时候做处理即可。

<div mx-view="app/views/list">
    <mx-loading from="index">loading content</mx-loading>
</div>
var combineTool = require('magix-combine');

combineTool.config({
    loaderType:'cmd', //or amd
    mxTagProcessor:function(tagInfo){
        // tagInfo.tag 获取去掉mx-的名称
        // tagInfo.attrs  获取属性
        // tagInfo.content 获取标签内容
         if(tagInfo.tag=='loading'){//获取标签名字,去掉mx-后的名字
            return '<div class="loading"><span></span><span></span><span></span></div>';
         }
        return '';//如果不能识别,则返回空字符串,删除掉这个标签
    }
});

使用magix-combine进行代码检测

模板

传递给mx-view参数

如果模板如下

<div mx-view="app/views/nav"
     view-valueKey="<%@params%>">
</div>

则会给出如下的提示:

avoid use view-valueKey at [filepath] use view-value-key instead more info: thx/magix#35

使用view-value-key代替view-valueKey

如果模板如下

<div mx-view="app/views/nav"
     view-value-key="abc_<%=params%>">
</div>

则会给出如下的提示
avoid use <%=params%> at [filepath] near mx-view="app/views/nav" use <%!params%> or <%@params%> instead

提示不要在参数中转义输出params参数,而是原样输出或引用输出。这是因为对于view参数,magix会原样传递,原本在html中要转义的输出,在参数传递时并不需要。所以该处按提示修改即可,如果非要转义输出,则需要在接收到的参数进行反转义,较麻烦

新窗口打开的链接

如果模板如下

<a href="some/link" target="_blank">open</a>

则会给出如下提示
add rel="noopener noreferrer" to <a href="some/link" target="_blank"> at [filepath] more info: asciidoctor/asciidoctor#2071

具体原因可参考提示信息中给出的链接

自定义事件

自定义的组件通常会自定义一些事件,如果模板如下

<div mx-view="app/gallery/dropdown"
     mx-listChange="listChange()">
</div>

则会给出如下提示
avoid use mx-listChagne at [filepath] use mx-listchagne instead more info: thx/magix#35

具体原因请参考提示信息中给出的链接

多次数据绑定

如果模板如下

<div mx-view="app/gallery/dropdown"
    <%:a%>
    <%:b%>></div>

当界面改变时,把界面上的数据同步给多个数据,这个目前无法支持,但会给出如下提示
unsupport multi bind:<div mx-view="app/gallery/dropdown" <%:a%> <%:b%>> at [filepath]

函数及for of与数据绑定

如果模板如下

<%_.each(list,(item)=>{%>
    <input value="<%:item%>" />
<%})%>

该写法会给出2个提示,第一个提示尽量不要用函数,如下
avoid use: (item)=>{} at [filepath] more info: thx/magix#37

第2个提示为无法分析出如何把界面变化的数据绑定到表达式<%:item%>上,如下
can not resolve expr: value="<%:a%>" at [filepath]

具体原因可参考:thx/magix#37

局部刷新拆分失败

如果模板如下

<div>
    <%var a=list.name%>
    <span><%=a%></span>
</div>
<div><%=a%> <%=list2%></div>

则会给出如下提示
segment failed at [filepath] more info: #21

具体原因可参考提示信息中的链接

如果模板如下

<div class="craft-content">
    <div id="sidebar">
        <% var i; %>
        <% for(i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
    <div class="article">
        <% for(i = 0;i < sections.length;++i) { %>
            <div><%=sections[i].text%></div>
        <% } %>
    </div>
</div>

不但会给出拆分失败,还会给出如下的提示信息
avoid reassign variable:i at [filepath]

不要对某个变量反复赋值

增强附属js的html、css文件的打包处理

目前在处理js附属的html和css时,是遇到同名的js、css、html时,只处理一次js文件,默认想法是把html、css都打包到js文件中

然而对于一些场景,可能并不能把css打包进去,这时候就需要把css文件输出到上线文件夹内。

样式的分类与处理

在magix项目中,我们把样式分为以下几类,同时针对每个类型,我们希望的加载方式与命名规则如下

normalize

描述:这种没有什么好说的,通常是针对标签来处理样式,使不同的浏览器表现一致。如cube的neat.css https://github.com/thx/cube/blob/gh-pages/src/css/neat.css 我们可以看到,里面全是针对标签处理的。

加载方式:这种在页面加载时就要放在head中加载好
命名规则:这种只是标签,针对标签书写样式就好

原子类的

描述:这种通常是在项目中非常常用的只含有一个样式规则的样式,如 https://github.com/twbs/bootstrap/blob/v4-dev/scss/_utilities.scss ,除此之外还有如mt5,mt10,pl5,pl10等,这些在阿里妈妈的项目中也是非常常见的。

加载方式:这种在页面加载时就要放在head中加载好
命名规则:以常见,简短为原则,如mt5,尽管第一次阅读有难度,但只要团队中约定好即可。

项目中通用的

描述:有些样式如列表、表单等,这些在管理类的后台中是高频页面,所以这些样式是项目通用的,应该在项目加载时就把它们加载好了。

加载方式:这种在页面加载时就要放在head中加载好
命名规则:功能模块+'-'+样式名称,如列表页面,我们用如 "list-selector"来命名样式

几个页面通用的

描述:有些样式只出现在几个特定的页面里,这种情况在项目中非常常见,你要说这个样式通用吧,但是它只在这几个页面里,而且你又不能把它附属于某一个页面。

加载方式:这种最好是采用动态加载,只有要展示这几个页面时才去加载
命名规则:同项目中通用的,使用功能模块+'-'+样式名称

当前页面使用的

描述:在某些页面,如签协议页面,它里面用到的样式,可能并不会在另的页面使用,因此这些样式只属于当前页面。

加载方式:这些样式的加载肯定是动态加载
命名规则:无要求,随意起名

样式的加载处理

其中全局提前加载的和最后当前页面使用的样式在magix项目中非常容易处理,使用Magix.applyStyle函数即可。

几个页面通用的,我们希望是动态加载的,如何动态加载,如何把对应的样式应用到这几个页面中是比较麻烦的。我们需要做以下几步。
如我们有一个group-handle.less样式文件,需要在几个页面中动态加载。

第一步,建立group-handle.js

这个文件仅仅是引用样式的,因为在magix项目中,所有的css和html都附属在js文件上,因此我们要建立一个js文件用来引用样式,这个group-handle.js可能的代码如下

var Magix=require('magix');
Magix.applyStyle('@group-handle.less');

第二步,在需要的页面require

在需要的页面require group-handle.js即可,但是这时候样式并不会应用到require group-handle.js的页面上,这是因为require信息全部是js的信息,magix-combine工具并不知道里面应用了哪些样式,如果要静态分析,工作量太大。

第三步,添加样式引用

完成第二步后,让magix-combine知道当前页面使用了哪些样式,我们使用[email protected]语法,最后的代码可能如下

'[email protected]';
var Magix=require('magix');
require('group-handle.less');
module.exports=Magix.View.extend({
    //...
});

总体来看这个方案比较繁琐,但是比较容易实施。我们来进行改进下

样式加载改进

其实magix-combine工具提供了非常多的处理钩子,我们针对require先做一个加工,比如我们用这个语法 require('css@./group-handle')来表示加载css文件所依附的js文件,同时自动添加ref@./group-handle.less语句,这样一来就用一条语句就完成了我们想要的功能。
在magix-combine的config中,添加resolveRequire的钩子

combintTool.config({
 //...
    resolveRequire: function(reqInfo) {
        //console.log(reqInfo);
        var styleReg = /^css@/;//针对我们的语法进行转换处理
        if (styleReg.test(reqInfo.dependedId)) {
            reqInfo.dependedId = reqInfo.dependedId.replace(styleReg, '');
            reqInfo.replacement = '"ref@' + reqInfo.dependedId + '.less";';
        }
    }
});

优化子模板的提取

考虑如下的代码:

<div mx-keys="a">
magix view default
</div>

虽然我们指定了当a变化时刷新div,但显然刷新是无意义的,所以这个刷新不需要,子模板也不需要分析

局部刷新与模板

模板选择

magix选择了underscoretemplate模板,简洁、代码少、强大。

模板改造

改造默认规则

默认underscore原样输出是<%=expr%>转义输出为<%~expr%>
magix在项目中的使用,改造为 原样输出<%!expr%> 转义输出为<%=expr%> 减少使用习惯带来的xss

同时增加了<%@ expr %>语法,只能用在view路径中,如<div mx-view="list?count=<%@count%>"></div>,在当前view中通过this.$updater.set({count:100});设置数据后,子view拿到相应的count时并不是字符串,而是你设置的数据类型。比如我传递一个对象,现在将变得非常简单,不用再动态渲染view

同时增加<%: expr %>用以界面向数据的更新,即双向绑定中的界面变化,同时数据也变化。如<input value="<%: inputValue%>" /> 当使用该功能时需要实现一个set<change>方法

最后一个需要magix-combine工具的支持,进行离线处理

改造with语句

默认underscoretemplate中使用with语句,这个with语句多少会影响性能,能去掉就去掉,在magix-combine工具中tmpl-vars.js插件就是用acorn进行模板分析,自动增加根数据对象,然后就可以去掉with了,开发者不改变开发习惯,由工具自动转换

改造datakey

以前的局部刷新需要写datakey用以指明当前如果需要更新关联的数据key,很早就想做一个自动化提取数据key的方案,但因为总总原因不能达成:比如在对子模板进行分析时,无法分析出子模板中哪些数据key是全局的和局部的,变量到底是局部的还是全局的需要对整个模板进行分析。

此次在改造with的基础上,因改造后全局的都会有固定的前置key,使得在对子模板分析时,很方便的获取当前子模板用到了哪些全局key

改造过程如下:对当前模板tmpl进行模板命令的移除,防止干扰分析。接着对每一个标签打上guid的标识,当然有些标签是不会处理的,具体见tmpl-guid插件。然后对带有guid的标签进行分析,分析时先去除带有guid的子标签及自闭合标签。剩余的则是当前标签拥有的,然后再对剩余的做数据key的提取即可。然后再递归分析当前标签的子内容即可。

这样用户无须在标签上写datakey及任何标识了,完全干净的模板内容,通过magix-combine背后给你强有力的支撑,从而完成想要的功能

输出合并的js内容给第三方工具使用

目前是从tmpl文件夹到src文件夹的方案进行合并的,需要支持从tmpl文件夹读取内容,但不写入到src文件夹。同时把处理好的文件内容交给第三方处理

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.