Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
deno
提交
8d977d01
D
deno
项目概览
张重言
/
deno
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
deno
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
8d977d01
编写于
11月 21, 2019
作者:
K
Kitson Kelly
提交者:
Ry Dahl
11月 20, 2019
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: Support named exports on bundles. (#3352)
上级
1912ed67
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
221 addition
and
71 deletion
+221
-71
cli/js/bundler.ts
cli/js/bundler.ts
+113
-0
cli/js/compiler.ts
cli/js/compiler.ts
+11
-55
cli/js/util.ts
cli/js/util.ts
+16
-0
cli/tests/bundle.test.out
cli/tests/bundle.test.out
+5
-1
cli/tests/integration_tests.rs
cli/tests/integration_tests.rs
+41
-0
deno_typescript/bundle_loader.js
deno_typescript/bundle_loader.js
+6
-9
std/manual.md
std/manual.md
+29
-6
未找到文件。
cli/js/bundler.ts
0 → 100644
浏览文件 @
8d977d01
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import
{
Console
}
from
"
./console.ts
"
;
import
*
as
dispatch
from
"
./dispatch.ts
"
;
import
{
sendSync
}
from
"
./dispatch_json.ts
"
;
import
{
TextEncoder
}
from
"
./text_encoding.ts
"
;
import
{
assert
,
commonPath
,
humanFileSize
}
from
"
./util.ts
"
;
import
{
writeFileSync
}
from
"
./write_file.ts
"
;
declare
global
{
const
console
:
Console
;
}
const
BUNDLE_LOADER
=
"
bundle_loader.js
"
;
const
encoder
=
new
TextEncoder
();
let
bundleLoader
:
string
;
let
rootExports
:
string
[]
|
undefined
;
/** Given a fileName and the data, emit the file to the file system. */
export
function
emitBundle
(
rootNames
:
string
[],
fileName
:
string
|
undefined
,
data
:
string
,
sourceFiles
:
readonly
ts
.
SourceFile
[]
):
void
{
// if the fileName is set to an internal value, just noop
if
(
fileName
&&
fileName
.
startsWith
(
"
$deno$
"
))
{
return
;
}
// This should never happen at the moment, but this code can't currently
// support it
assert
(
rootNames
.
length
===
1
,
"
Only single root modules supported for bundling.
"
);
if
(
!
bundleLoader
)
{
bundleLoader
=
sendSync
(
dispatch
.
OP_FETCH_ASSET
,
{
name
:
BUNDLE_LOADER
});
}
// when outputting to AMD and a single outfile, TypeScript makes up the module
// specifiers which are used to define the modules, and doesn't expose them
// publicly, so we have to try to replicate
const
sources
=
sourceFiles
.
map
(
sf
=>
sf
.
fileName
);
const
sharedPath
=
commonPath
(
sources
);
const
rootName
=
rootNames
[
0
].
replace
(
sharedPath
,
""
).
replace
(
/
\.\w
+$/i
,
""
);
let
instantiate
:
string
;
if
(
rootExports
&&
rootExports
.
length
)
{
instantiate
=
`const __rootExports = instantiate("
${
rootName
}
");\n`
;
for
(
const
rootExport
of
rootExports
)
{
if
(
rootExport
===
"
default
"
)
{
instantiate
+=
`export default __rootExports["
${
rootExport
}
"];\n`
;
}
else
{
instantiate
+=
`export const
${
rootExport
}
= __rootExports["
${
rootExport
}
"];\n`
;
}
}
}
else
{
instantiate
=
`instantiate("
${
rootName
}
");\n`
;
}
const
bundle
=
`
${
bundleLoader
}
\n
${
data
}
\n
${
instantiate
}
`
;
if
(
fileName
)
{
const
encodedData
=
encoder
.
encode
(
bundle
);
console
.
warn
(
`Emitting bundle to "
${
fileName
}
"`
);
writeFileSync
(
fileName
,
encodedData
);
console
.
warn
(
`
${
humanFileSize
(
encodedData
.
length
)}
emitted.`
);
}
else
{
console
.
log
(
bundle
);
}
}
/** Set the rootExports which will by the `emitBundle()` */
export
function
setRootExports
(
program
:
ts
.
Program
,
rootModules
:
string
[]
):
void
{
// get a reference to the type checker, this will let us find symbols from
// the AST.
const
checker
=
program
.
getTypeChecker
();
assert
(
rootModules
.
length
===
1
);
// get a reference to the main source file for the bundle
const
mainSourceFile
=
program
.
getSourceFile
(
rootModules
[
0
]);
assert
(
mainSourceFile
);
// retrieve the internal TypeScript symbol for this AST node
const
mainSymbol
=
checker
.
getSymbolAtLocation
(
mainSourceFile
);
if
(
!
mainSymbol
)
{
return
;
}
rootExports
=
checker
.
getExportsOfModule
(
mainSymbol
)
// .getExportsOfModule includes type only symbols which are exported from
// the module, so we need to try to filter those out. While not critical
// someone looking at the bundle would think there is runtime code behind
// that when there isn't. There appears to be no clean way of figuring that
// out, so inspecting SymbolFlags that might be present that are type only
.
filter
(
sym
=>
!
(
sym
.
flags
&
ts
.
SymbolFlags
.
Interface
||
sym
.
flags
&
ts
.
SymbolFlags
.
TypeLiteral
||
sym
.
flags
&
ts
.
SymbolFlags
.
Signature
||
sym
.
flags
&
ts
.
SymbolFlags
.
TypeParameter
||
sym
.
flags
&
ts
.
SymbolFlags
.
TypeAlias
||
sym
.
flags
&
ts
.
SymbolFlags
.
Type
||
sym
.
flags
&
ts
.
SymbolFlags
.
Namespace
||
sym
.
flags
&
ts
.
SymbolFlags
.
InterfaceExcludes
||
sym
.
flags
&
ts
.
SymbolFlags
.
TypeParameterExcludes
||
sym
.
flags
&
ts
.
SymbolFlags
.
TypeAliasExcludes
)
)
.
map
(
sym
=>
sym
.
getName
());
}
cli/js/compiler.ts
浏览文件 @
8d977d01
...
...
@@ -4,6 +4,7 @@
import
"
./globals.ts
"
;
import
"
./ts_global.d.ts
"
;
import
{
emitBundle
,
setRootExports
}
from
"
./bundler.ts
"
;
import
{
bold
,
cyan
,
yellow
}
from
"
./colors.ts
"
;
import
{
Console
}
from
"
./console.ts
"
;
import
{
core
}
from
"
./core.ts
"
;
...
...
@@ -12,13 +13,11 @@ import { cwd } from "./dir.ts";
import
*
as
dispatch
from
"
./dispatch.ts
"
;
import
{
sendAsync
,
sendSync
}
from
"
./dispatch_json.ts
"
;
import
*
as
os
from
"
./os.ts
"
;
import
{
TextEncoder
}
from
"
./text_encoding.ts
"
;
import
{
getMappedModuleName
,
parseTypeDirectives
}
from
"
./type_directives.ts
"
;
import
{
assert
,
notImplemented
}
from
"
./util.ts
"
;
import
*
as
util
from
"
./util.ts
"
;
import
{
window
}
from
"
./window.ts
"
;
import
{
postMessage
,
workerClose
,
workerMain
}
from
"
./workers.ts
"
;
import
{
writeFileSync
}
from
"
./write_file.ts
"
;
// Warning! The values in this enum are duplicated in cli/msg.rs
// Update carefully!
...
...
@@ -52,7 +51,6 @@ window["denoMain"] = denoMain;
const
ASSETS
=
"
$asset$
"
;
const
OUT_DIR
=
"
$deno$
"
;
const
BUNDLE_LOADER
=
"
bundle_loader.js
"
;
/** The format of the work message payload coming from the privileged side */
type
CompilerRequest
=
{
...
...
@@ -188,6 +186,12 @@ class SourceFile {
throw
new
Error
(
"
SourceFile has already been processed.
"
);
}
assert
(
this
.
sourceCode
!=
null
);
// we shouldn't process imports for files which contain the nocheck pragma
// (like bundles)
if
(
this
.
sourceCode
.
match
(
/
\/{2}\s
+@ts-nocheck/
))
{
util
.
log
(
`Skipping imports for "
${
this
.
filename
}
"`
);
return
[];
}
const
preProcessedFileInfo
=
ts
.
preProcessFile
(
this
.
sourceCode
,
true
,
true
);
this
.
processed
=
true
;
const
files
=
(
this
.
importedFiles
=
[]
as
Array
<
[
string
,
string
]
>
);
...
...
@@ -302,63 +306,12 @@ async function processImports(
return
sourceFiles
;
}
/** Utility function to turn the number of bytes into a human readable
* unit */
function
humanFileSize
(
bytes
:
number
):
string
{
const
thresh
=
1000
;
if
(
Math
.
abs
(
bytes
)
<
thresh
)
{
return
bytes
+
"
B
"
;
}
const
units
=
[
"
kB
"
,
"
MB
"
,
"
GB
"
,
"
TB
"
,
"
PB
"
,
"
EB
"
,
"
ZB
"
,
"
YB
"
];
let
u
=
-
1
;
do
{
bytes
/=
thresh
;
++
u
;
}
while
(
Math
.
abs
(
bytes
)
>=
thresh
&&
u
<
units
.
length
-
1
);
return
`
${
bytes
.
toFixed
(
1
)}
${
units
[
u
]}
`
;
}
/** Ops to rest for caching source map and compiled js */
function
cache
(
extension
:
string
,
moduleId
:
string
,
contents
:
string
):
void
{
util
.
log
(
"
compiler::cache
"
,
{
extension
,
moduleId
});
sendSync
(
dispatch
.
OP_CACHE
,
{
extension
,
moduleId
,
contents
});
}
const
encoder
=
new
TextEncoder
();
/** Given a fileName and the data, emit the file to the file system. */
function
emitBundle
(
rootNames
:
string
[],
fileName
:
string
|
undefined
,
data
:
string
,
sourceFiles
:
readonly
ts
.
SourceFile
[]
):
void
{
// For internal purposes, when trying to emit to `$deno$` just no-op
if
(
fileName
&&
fileName
.
startsWith
(
"
$deno$
"
))
{
console
.
warn
(
"
skipping emitBundle
"
,
fileName
);
return
;
}
const
loader
=
fetchAsset
(
BUNDLE_LOADER
);
// when outputting to AMD and a single outfile, TypeScript makes up the module
// specifiers which are used to define the modules, and doesn't expose them
// publicly, so we have to try to replicate
const
sources
=
sourceFiles
.
map
(
sf
=>
sf
.
fileName
);
const
sharedPath
=
util
.
commonPath
(
sources
);
rootNames
=
rootNames
.
map
(
id
=>
id
.
replace
(
sharedPath
,
""
).
replace
(
/
\.\w
+$/i
,
""
)
);
const
instantiate
=
`instantiate(
${
JSON
.
stringify
(
rootNames
)}
);\n`
;
const
bundle
=
`
${
loader
}
\n
${
data
}
\n
${
instantiate
}
`
;
if
(
fileName
)
{
const
encodedData
=
encoder
.
encode
(
bundle
);
console
.
warn
(
`Emitting bundle to "
${
fileName
}
"`
);
writeFileSync
(
fileName
,
encodedData
);
console
.
warn
(
`
${
humanFileSize
(
encodedData
.
length
)}
emitted.`
);
}
else
{
console
.
log
(
bundle
);
}
}
/** Returns the TypeScript Extension enum for a given media type. */
function
getExtension
(
fileName
:
string
,
mediaType
:
MediaType
):
ts
.
Extension
{
switch
(
mediaType
)
{
...
...
@@ -577,7 +530,7 @@ class Host implements ts.CompilerHost {
try
{
assert
(
sourceFiles
!=
null
);
if
(
this
.
_requestType
===
CompilerRequestType
.
Bundle
)
{
emitBundle
(
this
.
_rootNames
,
this
.
_outFile
,
data
,
sourceFiles
!
);
emitBundle
(
this
.
_rootNames
,
this
.
_outFile
,
data
,
sourceFiles
);
}
else
{
assert
(
sourceFiles
.
length
==
1
);
const
url
=
sourceFiles
[
0
].
fileName
;
...
...
@@ -704,6 +657,9 @@ window.compilerMain = function compilerMain(): void {
// warning so it goes to stderr instead of stdout
console
.
warn
(
`Bundling "
${
resolvedRootModules
.
join
(
`", "`
)}
"`
);
}
if
(
request
.
type
===
CompilerRequestType
.
Bundle
)
{
setRootExports
(
program
,
resolvedRootModules
);
}
const
emitResult
=
program
.
emit
();
emitSkipped
=
emitResult
.
emitSkipped
;
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
...
...
cli/js/util.ts
浏览文件 @
8d977d01
...
...
@@ -248,3 +248,19 @@ export function commonPath(paths: string[], sep = "/"): string {
const
prefix
=
parts
.
slice
(
0
,
endOfPrefix
).
join
(
sep
);
return
prefix
.
endsWith
(
sep
)
?
prefix
:
`
${
prefix
}${
sep
}
`
;
}
/** Utility function to turn the number of bytes into a human readable
* unit */
export
function
humanFileSize
(
bytes
:
number
):
string
{
const
thresh
=
1000
;
if
(
Math
.
abs
(
bytes
)
<
thresh
)
{
return
bytes
+
"
B
"
;
}
const
units
=
[
"
kB
"
,
"
MB
"
,
"
GB
"
,
"
TB
"
,
"
PB
"
,
"
EB
"
,
"
ZB
"
,
"
YB
"
];
let
u
=
-
1
;
do
{
bytes
/=
thresh
;
++
u
;
}
while
(
Math
.
abs
(
bytes
)
>=
thresh
&&
u
<
units
.
length
-
1
);
return
`
${
bytes
.
toFixed
(
1
)}
${
units
[
u
]}
`
;
}
cli/tests/bundle.test.out
浏览文件 @
8d977d01
...
...
@@ -14,5 +14,9 @@ define("mod1", ["require", "exports", "subdir2/mod2"], function (require, export
[WILDCARD]
});
instantiate(["mod1"]);
const __rootExports = instantiate("mod1");
export const returnsHi = __rootExports["returnsHi"];
export const returnsFoo2 = __rootExports["returnsFoo2"];
export const printHello3 = __rootExports["printHello3"];
export const throwsError = __rootExports["throwsError"];
cli/tests/integration_tests.rs
浏览文件 @
8d977d01
...
...
@@ -55,6 +55,47 @@ fn js_unit_tests() {
drop
(
g
);
}
#[test]
fn
bundle_exports
()
{
use
tempfile
::
TempDir
;
// First we have to generate a bundle of some module that has exports.
let
mod1
=
util
::
root_path
()
.join
(
"cli/tests/subdir/mod1.ts"
);
assert
!
(
mod1
.is_file
());
let
t
=
TempDir
::
new
()
.expect
(
"tempdir fail"
);
let
bundle
=
t
.path
()
.join
(
"mod1.bundle.js"
);
let
mut
deno
=
util
::
deno_cmd
()
.current_dir
(
util
::
root_path
())
.arg
(
"bundle"
)
.arg
(
mod1
)
.arg
(
&
bundle
)
.spawn
()
.expect
(
"failed to spawn script"
);
let
status
=
deno
.wait
()
.expect
(
"failed to wait for the child process"
);
assert
!
(
status
.success
());
assert
!
(
bundle
.is_file
());
// Now we try to use that bundle from another module.
let
test
=
t
.path
()
.join
(
"test.js"
);
std
::
fs
::
write
(
&
test
,
"
import { printHello3 } from
\"
./mod1.bundle.js
\"
;
printHello3(); "
,
)
.expect
(
"error writing file"
);
let
output
=
util
::
deno_cmd
()
.current_dir
(
util
::
root_path
())
.arg
(
"run"
)
.arg
(
&
test
)
.output
()
.expect
(
"failed to spawn script"
);
// check the output of the test.ts program.
assert_eq!
(
std
::
str
::
from_utf8
(
&
output
.stdout
)
.unwrap
()
.trim
(),
"Hello"
);
assert_eq!
(
output
.stderr
,
b
""
);
}
// TODO(#2933): Rewrite this test in rust.
#[test]
fn
repl_test
()
{
...
...
deno_typescript/bundle_loader.js
浏览文件 @
8d977d01
...
...
@@ -6,14 +6,16 @@
// bundles when creating snapshots, but is also used when emitting bundles from
// Deno cli.
// @ts-nocheck
/**
* @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void}
* @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void
=
}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let
define
;
/**
* @type {(mod: string
| string[]) => void
}
* @type {(mod: string
) => any=
}
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let
instantiate
;
...
...
@@ -111,14 +113,9 @@ let instantiate;
instantiate
=
dep
=>
{
define
=
undefined
;
if
(
Array
.
isArray
(
dep
))
{
for
(
const
d
of
dep
)
{
getExports
(
d
);
}
}
else
{
getExports
(
dep
);
}
const
result
=
getExports
(
dep
);
// clean up, or otherwise these end up in the runtime environment
instantiate
=
undefined
;
return
result
;
};
})();
std/manual.md
浏览文件 @
8d977d01
...
...
@@ -797,27 +797,50 @@ Particularly useful ones:
dependencies of the specified input. For example:
```
> deno bundle https://deno.land/std/examples/colors.ts
> deno bundle https://deno.land/std/examples/colors.ts
colors.bundle.js
Bundling "colors.bundle.js"
Emitting bundle to "colors.bundle.js"
9.2 kB emitted.
```
If you omit the out file, the bundle will be sent to
`stdout`
.
The bundle can just be run as any other module in Deno would:
```
deno colors.bundle.js
```
Bundles can also be loaded in the web browser. For example:
The output is a self contained ES Module, which any exports from the main module
supplied on the command line will be available. For example if the main module
looked something like this:
```
ts
export
{
foo
}
from
"
./foo.js
"
;
export
const
bar
=
"
bar
"
;
```
It could be imported like this:
```
ts
import
{
foo
,
bar
}
from
"
./lib.bundle.js
"
;
```
Bundles can also be loaded in the web browser. The bundle is a self-contained ES
module, and so the attribute of
`type`
must be set to
`"module"`
. For example:
```
html
<script
src=
"website.bundle.js"
></script>
<script
type=
"module"
src=
"website.bundle.js"
></script>
```
Bundles, whether loaded in the web browser, or in Deno, would run the root
module which is specified on the command line when creating the bundle, so put
any initiation logic in that module.
Or you could import it into another ES module to consume:
```
html
<script
type=
"module"
>
import
*
as
website
from
"
website.bundle.js
"
;
</script>
```
### Installing executable scripts
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录