原文 https://code.thheller.com/blo...
原做者是 shadow-cljs 做者, shadow-cljs 是一个面向 JavaScript 开发者友好的 ClojureScript 编译器.html
以前关于 js 依赖的文章(问题, 前景)里面, 我解释了为何 shadow-cljs
当中采用了和 ClojureScript 默认的方案不一样的作法. 简单回顾下:node
cljsjs
或者 :foreign-libs
的写法难以扩张react
自定义的打包实际当中使用繁琐git
Closure Compiler 目前对大部分的 npm 模块的处理不够可靠github
shadow-cljs
自定义了一个 js bundler, 而移除了 :foreign-libs
的支持npm
几乎全部的 npm 模块都会写一遍如何安装. 如今对于 shadow-cljs
来讲也是适用的. 好比有个类库要你运行:json
npm install the-thing
你照作就好. 不须要其余步骤了. 固然你喜欢的话能够用 yarn
. 而后依赖就会被写进 package.json
文件用于管理. 若是没有 package.json
那就运行 npm init
.浏览器
上面说到这些东西, 你能够用这个 QuickStart 模板 来试用.bash
大部分的 npm 模块也会写一下具体的代码表示怎样使用模块. "旧的" CommonJS 的写法是用 require
调用. 翻译到 ClojureScript 就是:app
var react = require('react');
(ns my.app (:require ["react" :as react]))
无论 "string"
参数的地方用了什么而后被 require
调用, 咱们都是这样换成 ns
:require
. :as
的 alias 部分就随你定义. 有了这个以后, 它就像是其余的 cljs 的命名空间那样能够调用了:
(react/createElement "div" nil "helle world")
这跟之前 :foreign-libs
或者 CLJSJS
当中作的不同, 之前好比引入了 thing
到 ns
而后要用 js/Thing
(或者其余全局导出的变量)来使用代码. 如今能够用 ns
格式以及 :as
后面提供的名称. 须要的话还能够写 :refer
和 :rename
.
一些模块会暴露一个函数, 那你能够写 (:require ["thing" as thing])
而后调用 (thing)
.
最近一些模块开始用 ES6 的 import
语法做为例子了. 这些代码除了一个 default
写法之外, 基本上在 ClojureScript 能作到一一对应. 好比说翻译下面的例子:
import defaultExport from "module-name"; import * as name from "module-name"; import { export } from "module-name"; import { export as alias } from "module-name"; import { export1 , export2 } from "module-name"; import { export1 , export2 as alias2 , [...] } from "module-name"; import defaultExport, { export [ , [...] ] } from "module-name"; import defaultExport, * as name from "module-name"; import "module-name";
到(包裹在 ns
里面的):
(:require ["module-name" :default defaultExport]) (:require ["module-name" :as name]) (:require ["module-name" :refer (export)]) (:require ["module-name" :rename {export alias}]) (:require ["module-name" :refer (export1) :rename {export2 alias2}]) (:require ["module-name" :refer (export) :default defaultExport]) (:require ["module-name" :as name :default defaultExport]) (:require ["module-name"])
其中 :default
参数目前只在 shadow-cljs
里面支持, 可是你也能够在这里投票帮助它进入到规范当中. 或者你也能够一直用 :as alias
而后调用 alias/default
, 这样你以为能个标准的 cljs 始终保持兼容的话. 我以为吧, 对于某些模块来讲啰嗦了点.
以前咱们的使用打包以后的代码, 可能会包含咱们用不到的代码. 某些模块也说明了一些办法能够只引入部分的模块, 这样最终构建的代码体积会小一些.
react-virtualized 有个这样的例子:
// You can import any component you want as a named export from 'react-virtualized', eg import { Column, Table } from 'react-virtualized' // But if you only use a few react-virtualized components, // And you're concerned about increasing your application's bundle size, // You can directly import only the components you need, like so: import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer' import List from 'react-virtualized/dist/commonjs/List'
那么很容易翻译过去:
;; all (:require ["react-virtualized" :refer (Column Table)]) ;; one by one (:require ["react-virtualized/dist/commonjs/AutoSizer" :default virtual-auto-sizer]) (:require ["react-virtualized/dist/commonjs/List" :default virtual-list])
默认状况下 shadow-cljs
经过 npm 的方式引用查找全部 (:require ["thing" :as x])
. 也就是说会查找 <project>/node_modules/thing/...
当中的代码. 为了对这个行为进行自定义, shadow-cljs
暴露了一个 :resolve
配置项, 你能够本身定义某些模块如何查找.
好比说页面里的 React
从 CDN 上引用了. 这时候按说你能够用 js/React
了, 可是最好仍是不要这样. 你应该是继续用 (:require ["react" :as react])
, 同时在 shadow-cljs
里定义 react
怎样查找. 这个配置在 shadow-cljs.edn
文件里配置:
{:builds {:app {:target :browser ... :js-options {:resolve {"react" {:target :global :global "React"}}}} :server {:target :node-script ...}}}
如今 :app
这个构建会使用全局的 React
实例, 而在 :server
这个构建当中会继续使用 react
的 npm 模块. 不须要额外折腾代码去完成需求.
require
某些模块提供多个 "dist" 文件, 而后可能默认的那个恰好是在 shadow-cljs
里有问题的. 一个明显的例子就是 d3
. 他们默认的 "main"
指向 build/d3.node.js
, 这个不是在浏览器里面用的版本. 他们的 ES6 代码还触发了 Closure Compiler 里的一个 bug, 因此咱们不能用. 这样的话咱们就重定向到其余的引用去:
{:resolve {"d3" {:target :npm :require "d3/build/d3.js"}}}
你也能够直接就写 (:require ["d3/build/d3.js" :as d3])
, 若是你只关心浏览器当中的使用的话.
你还能够用 :resolve
来直接映射到一个项目中的本地文件:
{:resolve {"my-thing" {:target :file :file "path/to/file.js"}}}
这里的 :file
老是相对于项目根路径. 这个文件里能够用 require
或者 import/export
, 这些随后都会被处理好的.
不少 cljs 类库还在用 CLJSJS
包, 它们在 shadow-cljs
里不能正常使用了, 由于 :foreign-libs
再也不支持. 我提供了一个清晰的迁移路线, 只须要增长一个 shim 文件把 cljsjs.thing
映射回到原始的 npm
模块, 而后把全局变量暴露出去.
好比 react
须要一个这样的文件src/cljsjs/react.cljs:
(ns cljsjs.react (:require ["react" :as react] ["create-react-class" :as crc])) (js/goog.object.set react "createClass" crc) (js/goog.exportSymbol "React" react)
由于这样的话每一个人手动处理会麻烦, 因此我提供了 shadow-cljsjs
这个类库来提供这个功能. 虽然不会包含每个模块, 可是我会持续添加. 欢迎来帮忙贡献模块.
不过它仅仅提供 shim 文件. 你仍是须要用 npm install
安装真实的模块.
JavaScript 社区变化很快, 并非每一个人都同样地写代码, 都同样地分发代码, 有些东西是 shadow-cljs
不能自动处理或者须要自定义 :resolve
配置的. 多是会遇到 bug, 毕竟都是新东西.
遇到任何模块不能按照预期地使用, 请报告. 在 #shadow-cljs 很容易找到我.
关于这篇文章的讨论请移步 :clojurevese.