Refactor scripts (#2465)
* Refactor all kinds of scripts * Update docs to ensure linter passes
This commit is contained in:
parent
62172cbe08
commit
48e7fd1711
23 changed files with 1695 additions and 340 deletions
|
|
@ -466,7 +466,7 @@ var Modal = {
|
|||
|
||||
If you do it like above, you could run into issues when using it:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
var MyModal = {
|
||||
view: function() {
|
||||
return m(Modal, {
|
||||
|
|
@ -483,7 +483,7 @@ var MyModal = {
|
|||
|
||||
Instead, you should forward *single* attributes into vnodes:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// PREFER
|
||||
var Modal = {
|
||||
// ...
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
var marked = require("marked")
|
||||
var layout = fs.readFileSync("./docs/layout.html", "utf-8")
|
||||
var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version
|
||||
try {fs.mkdirSync("./dist")} catch (e) {/* ignore */}
|
||||
try {fs.mkdirSync("./dist/archive")} catch (e) {/* ignore */}
|
||||
try {fs.mkdirSync("./dist/archive/v" + version)} catch (e) {/* ignore */}
|
||||
|
||||
var guides = fs.readFileSync("docs/nav-guides.md", "utf-8")
|
||||
var methods = fs.readFileSync("docs/nav-methods.md", "utf-8")
|
||||
|
||||
generate("docs")
|
||||
|
||||
function generate(pathname) {
|
||||
if (fs.lstatSync(pathname).isDirectory()) {
|
||||
fs.readdirSync(pathname).forEach(function(filename) {
|
||||
generate(pathname + "/" + filename)
|
||||
})
|
||||
}
|
||||
else if (!pathname.match(/tutorials|archive|nav-/)) {
|
||||
if (pathname.match(/\.md$/)) {
|
||||
var outputFilename = pathname.replace(/\.md$/, ".html")
|
||||
var markdown = fs.readFileSync(pathname, "utf-8")
|
||||
var anchors = {}
|
||||
var fixed = markdown
|
||||
.replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags
|
||||
return "<code>" + (a + b + c).replace(/\|/g, "|") + "</code>"
|
||||
})
|
||||
.replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav) { // inject menu
|
||||
var file = path.basename(pathname)
|
||||
var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))")
|
||||
var replace = function(match, space, li, link) {
|
||||
return space + li + "**" + link + "**" + (nav ? "\n" + nav.replace(/(^|\n)/g, "$1\t" + space) : "")
|
||||
}
|
||||
var modified = guides.match(link) ? guides.replace(link, replace) : methods.replace(link, replace)
|
||||
return title + modified + "\n\n"
|
||||
})
|
||||
.replace(/(\]\([^\)]+)(\.md)/gim, function(match, path, extension) {
|
||||
return path + (path.match(/http/) ? extension : ".html")
|
||||
}) // fix links
|
||||
var markedHtml = marked(fixed)
|
||||
.replace(/(\W)Array<([^/<]+?)>/gim, "$1Array<$2>") // Fix type signatures containing Array<...>
|
||||
var title = fixed.match(/^#([^\n\r]+)/i) || []
|
||||
var html = layout
|
||||
.replace(/<title>Mithril\.js<\/title>/, "<title>" + title[1] + " - Mithril.js</title>")
|
||||
.replace(/\[version\]/g, version) // update version
|
||||
.replace(/\[body\]/, markedHtml)
|
||||
.replace(/<h(.) id="([^"]+?)">(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors
|
||||
var anchor = text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/<a.*?>.+?<\/a>/g, "").replace(/\.|\[|\]|"|\/|\(|\)/g, "").replace(/\s/g, "-");
|
||||
|
||||
if(anchor in anchors) {
|
||||
anchor += ++anchors[anchor]
|
||||
} else {
|
||||
anchors[anchor] = 0;
|
||||
}
|
||||
|
||||
return `<h${n} id="${anchor}"><a href="#${anchor}">${text}</a></h${n}>`;
|
||||
})
|
||||
fs.writeFileSync("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
fs.writeFileSync("./dist/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
}
|
||||
else if (!pathname.match(/lint|generate/)) {
|
||||
var encoding = (/\.(ico|png)$/i).test(path.extname(pathname)) ? "binary" : "utf-8";
|
||||
fs.writeFileSync("./dist/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding)
|
||||
fs.writeFileSync("./dist/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -215,7 +215,7 @@ m("a-scene", [
|
|||
])
|
||||
```
|
||||
|
||||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](https://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property.
|
||||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](http://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property.
|
||||
|
||||
```javascript
|
||||
m("brick-deck[selected-index=0]", [/* ... */]) // lowercase
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ $ npm install webpack webpack-cli --save-dev
|
|||
```
|
||||
|
||||
3. Add a "start" entry to the scripts section in `package.json`.
|
||||
```js
|
||||
```javascript
|
||||
{
|
||||
// ...
|
||||
"scripts": {
|
||||
|
|
@ -61,7 +61,7 @@ $ npm install webpack webpack-cli --save-dev
|
|||
```
|
||||
|
||||
4. Create `src/index.js` file.
|
||||
```js
|
||||
```javascript
|
||||
import m from "mithril";
|
||||
m.render(document.body, "hello world");
|
||||
```
|
||||
|
|
@ -113,7 +113,7 @@ m.render(document.body, "hello world")
|
|||
|
||||
Modularization is the practice of separating the code into files. Doing so makes it easier to find code, understand what code relies on what code, and test.
|
||||
|
||||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](https://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](https://rollupjs.org/) or [Babel](https://babeljs.io/).
|
||||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](https://rollupjs.org/) or [Babel](https://babeljs.io/).
|
||||
|
||||
Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single JavaScript file before running in a client-side application.
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ npm install webpack webpack-cli --save-dev
|
|||
|
||||
Open the `package.json` that you created earlier, and add an entry to the `scripts` section:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"scripts": {
|
||||
|
|
@ -199,7 +199,7 @@ If you open bin/app.js, you'll notice that the Webpack bundle is not minified, s
|
|||
|
||||
You can use hooks in your production environment to run the production build script automatically. Here's an example for [Heroku](https://www.heroku.com/):
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ JSX and hyperscript are two different syntaxes you can use for specifying vnodes
|
|||
|
||||
You can see the tradeoffs come into play in more complex trees. For instance, consider this hyperscript tree, adapted from a real-world project by [@isiahmeadows](https://github.com/isiahmeadows/) with some alterations for clarity and readability:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
function SummaryView() {
|
||||
let tag, posts
|
||||
|
||||
|
|
|
|||
186
docs/lint.js
186
docs/lint.js
|
|
@ -1,186 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
var http = require("http")
|
||||
var url = require("url")
|
||||
|
||||
//lint rules
|
||||
function lint(file, data) {
|
||||
ensureCodeIsHighlightable(file, data)
|
||||
ensureCodeIsSyntaticallyValid(file, data)
|
||||
ensureCodeIsRunnable(file, data)
|
||||
ensureCommentStyle(file, data)
|
||||
ensureLinkIsValid(file, data)
|
||||
}
|
||||
|
||||
function ensureCodeIsHighlightable(file, data) {
|
||||
var codeBlocks = data.match(/```(.|\n|\r)*?```/gim) || []
|
||||
codeBlocks.forEach(function(block) {
|
||||
block = block.slice(3, -3)
|
||||
if (block.indexOf("javascript") !== 0) {
|
||||
try {if (new Function(block)) console.log(file + " - javascript block missing language tag after triple backtick\n\n" + block + "\n\n---\n\n")}
|
||||
catch (e) {/*not a js block, ignore*/}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function ensureCodeIsSyntaticallyValid(file, data) {
|
||||
var codeBlocks = data.match(/```javascript(.|\n|\r)*?```/gim) || []
|
||||
codeBlocks.forEach(function(block) {
|
||||
block = block.slice(13, -3)
|
||||
try {new Function(block)}
|
||||
catch (e) {console.log(file + " - javascript block has wrong syntax\n\n" + e.message + "\n\n" + block + "\n\n---\n\n")}
|
||||
})
|
||||
}
|
||||
|
||||
function ensureCodeIsRunnable(file, data) {
|
||||
var codeBlocks = data.match(/```javascript(.|\n|\r)*?```/gim) || []
|
||||
var code = codeBlocks.map(function(block) {return block.slice(13, -3)}).join(";")
|
||||
|
||||
//stubs
|
||||
var silentConsole = {log: function() {}}
|
||||
var fetch = function() {
|
||||
return Promise.resolve({
|
||||
json: function() {}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
initMocks()
|
||||
var module = {exports: {}}
|
||||
new Function("console,fetch,module,require", code).call(this, silentConsole, fetch, module, function(dep) {
|
||||
if (dep.indexOf("./mycomponent") === 0) return {view: function() {}}
|
||||
if (dep.indexOf("mithril/ospec/ospec") === 0) return global.o
|
||||
if (dep.indexOf("mithril/stream") === 0) return global.stream
|
||||
if (dep === "mithril") return global.m
|
||||
|
||||
if (dep === "../model/User") return {
|
||||
list: [],
|
||||
current: {},
|
||||
loadList: function() {
|
||||
return Promise.resolve({data: []})
|
||||
},
|
||||
load: function() {
|
||||
return Promise.resolve({firstName: "", lastName: ""})
|
||||
},
|
||||
save: function() {
|
||||
return Promise.resolve()
|
||||
},
|
||||
}
|
||||
if (dep === "./view/UserList") return {view: function() {}}
|
||||
if (dep === "./view/UserForm") return {view: function() {}}
|
||||
if (dep === "./view/Layout") return {view: function() {}}
|
||||
})
|
||||
}
|
||||
catch (e) {console.log(file + " - javascript code cannot run\n\n" + e.stack + "\n\n" + code + "\n\n---\n\n")}
|
||||
}
|
||||
|
||||
function ensureCommentStyle(file, data) {
|
||||
var codeBlocks = data.match(/```javascript(.|\n|\r)*?```/gim) || []
|
||||
codeBlocks.forEach(function(block) {
|
||||
block = block.slice(13, -3)
|
||||
if (block.match(/(^|\s)\/\/[\S]/)) console.log(file + " - comment missing space\n\n" + block + "\n\n---\n\n")
|
||||
})
|
||||
}
|
||||
|
||||
function ensureLinkIsValid(file, data) {
|
||||
var links = data.match(/\]\(([^\)]+?)\)/gim) || []
|
||||
links.forEach(function(match) {
|
||||
var link = match.slice(2, -1)
|
||||
var path = (link.match(/[\w-#]+\.md/) || [])[0]
|
||||
if (link.match(/http/)) {
|
||||
var u = url.parse(link)
|
||||
http.request({method: "HEAD", host: u.host, path: u.pathname, port: 80}).on("error", function() {
|
||||
console.log(file + " - broken external link: " + link)
|
||||
})
|
||||
}
|
||||
else if (path && !fs.existsSync("docs/" + path)) console.log(file + " - broken link: " + link)
|
||||
})
|
||||
}
|
||||
|
||||
function initMocks() {
|
||||
/* eslint-disable global-require */
|
||||
global.window = require("../test-utils/browserMock")()
|
||||
global.document = window.document
|
||||
global.m = require("../index")
|
||||
global.o = require("../ospec/ospec")
|
||||
global.stream = require("../stream")
|
||||
global.alert = function() {}
|
||||
/* eslint-enable global-require */
|
||||
|
||||
//routes consumed by request.md
|
||||
global.window.$defineRoutes({
|
||||
"GET /api/v1/users": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{name: ""}])}
|
||||
},
|
||||
"GET /api/v1/users/search": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{id: 1, name: ""}])}
|
||||
},
|
||||
"GET /api/v1/users/1/projects": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{id: 1, name: ""}])}
|
||||
},
|
||||
"GET /api/v1/todos": function() {
|
||||
return {status: 200, responseText: JSON.stringify([])}
|
||||
},
|
||||
"PUT /api/v1/users/1": function(request) {
|
||||
return {status: 200, responseText: request.query.callback ? request.query.callback + "([])" : "[]"}
|
||||
},
|
||||
"POST /api/v1/upload": function() {
|
||||
return {status: 200, responseText: JSON.stringify([])}
|
||||
},
|
||||
"GET /files/icon.svg": function() {
|
||||
return {status: 200, responseText: "<svg></svg>"}
|
||||
},
|
||||
"GET /files/data.csv": function() {
|
||||
return {status: 200, responseText: "a,b,c"}
|
||||
},
|
||||
"GET /api/v1/users/123": function() {
|
||||
return {status: 200, responseText: JSON.stringify({id: 123})}
|
||||
},
|
||||
"GET /api/v1/users/foo:bar": function() {
|
||||
return {status: 200, responseText: JSON.stringify({id: 123})}
|
||||
},
|
||||
"GET /files/image.svg": function() {
|
||||
return {status: 200, responseText: "<svg></svg>"}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
//runner
|
||||
function traverseDirectory(pathname, callback) {
|
||||
pathname = pathname.replace(/\\/g, "/")
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.lstat(pathname, function(err, stat) {
|
||||
if (err) reject(err)
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdir(pathname, function(err, pathnames) {
|
||||
if (err) reject(err)
|
||||
var promises = []
|
||||
for (var i = 0; i < pathnames.length; i++) {
|
||||
pathnames[i] = path.join(pathname, pathnames[i])
|
||||
promises.push(traverseDirectory(pathnames[i], callback))
|
||||
}
|
||||
callback(pathname, stat, pathnames)
|
||||
resolve(Promise.all(promises))
|
||||
})
|
||||
}
|
||||
else {
|
||||
callback(pathname, stat)
|
||||
resolve(pathname)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//run
|
||||
traverseDirectory("./docs", function(pathname) {
|
||||
if (pathname.indexOf(".md") > -1 && !pathname.match(/change-log|migration-|node_modules/)) {
|
||||
fs.readFile(pathname, "utf8", function(err, data) {
|
||||
if (err) console.log(err)
|
||||
else lint(pathname, data)
|
||||
})
|
||||
}
|
||||
})
|
||||
.then(process.exit)
|
||||
|
|
@ -349,7 +349,7 @@ m("div", m(Component, "value", function(key) { return "child" }))
|
|||
|
||||
In v0.2.x, the children of DOM nodes were represented literally with no normalization aside from using the children directly if only a single array child is present. It returned a structure more like this, with the strings represented literally.
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", "value", ["nested"])
|
||||
|
||||
// Becomes:
|
||||
|
|
@ -365,7 +365,7 @@ m("div", "value", ["nested"])
|
|||
|
||||
In v2.x, children of DOM vnodes are normalized to objects of a single consistent structure.
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", "value", ["nested"])
|
||||
|
||||
// Becomes roughly:
|
||||
|
|
@ -383,7 +383,7 @@ m("div", "value", ["nested"])
|
|||
|
||||
If only a single text child is present on a DOM vnode, it instead sets `text` to that value.
|
||||
|
||||
```js
|
||||
```javascript
|
||||
m("div", "value")
|
||||
|
||||
// Becomes roughly:
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ m(m.route.Link, {
|
|||
}, "link name")
|
||||
```
|
||||
|
||||
This supports full accessibility for both `a` and `button`, via a `disabled` attribute. This ensures [no `href` attribute or `onclick` handler is set](https://css-tricks.com/how-to-disable-links/) and that an `"aria-disabled": "true"` attribute *is* set. If you are passing an `onclick` handler already, that's dropped. (You can work around this by adding it directly in a [lifecycle hook](lifecycle.md).) The `disabled` attribute is itself proxied to the element or component, so you can disable routed `<button>`s and the like.
|
||||
This supports full accessibility for both `a` and `button`, via a `disabled` attribute. This ensures [no `href` attribute or `onclick` handler is set](https://css-tricks.com/how-to-disable-links/) and that an `"aria-disabled": "true"` attribute *is* set. If you are passing an `onclick` handler already, that's dropped. (You can work around this by adding it directly in a [lifecycle hook](lifecycle-methods.md).) The `disabled` attribute is itself proxied to the element or component, so you can disable routed `<button>`s and the like.
|
||||
|
||||
```javascript
|
||||
// This does the right thing and the accessible thing for you.
|
||||
|
|
@ -241,7 +241,7 @@ As a rule of thumb, RouteResolvers should be in the same file as the `m.route` c
|
|||
|
||||
When using components, you could think of them as special sugar for this route resolver, assuming your component is `Home`:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
var routeResolver = {
|
||||
onmatch: function() { return Home },
|
||||
render: function(vnode) { return [vnode] },
|
||||
|
|
@ -803,16 +803,13 @@ m.route(document.body, "/", {
|
|||
|
||||
However, realistically, in order for that to work on a production scale, it would be necessary to bundle all of the dependencies for the `Home.js` module into the file that is ultimately served by the server.
|
||||
|
||||
Fortunately, there are a number of tools that facilitate the task of bundling modules for lazy loading. Here's an example using [webpack's code splitting system](https://webpack.github.io/docs/code-splitting.html):
|
||||
Fortunately, there are a number of tools that facilitate the task of bundling modules for lazy loading. Here's an example using [native dynamic `import(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import), supported by many bundlers:
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function() {
|
||||
// using Webpack async code splitting
|
||||
return new Promise(function(resolve) {
|
||||
require(['./Home.js'], resolve)
|
||||
})
|
||||
return import('./Home.js')
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ module.exports = MyComponent
|
|||
|
||||
You could easily create a few unit tests for that.
|
||||
|
||||
```js
|
||||
```javascript
|
||||
var mq = require("mithril-query")
|
||||
var MyComponent = require("./MyComponent")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue