原文 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.