Fork me on GitHub
#shadow-cljs
<
2017-10-13
>
thheller06:10:59

ah right. I only tested with foo.html which automatically sets content-type based on file ext 😛

thheller09:10:52

haha … I had an idea for externs yesterday and it might actually work 🙂

thheller09:10:47

probably a really bad idea but I’m gonna test the size difference on my work project to check how bad

mhuebert09:10:15

one question, how do transitive JS deps work? eg. I have a commands library that will depend on keypress.js as a github dependency in package.json, and am wondering what I do in that library so that I can depend on it elsewhere

thheller09:10:26

yeah thats unsolved at the moment

thheller09:10:06

CLJS wants to do it this way: deps.cljs in your src root, {:npm-deps {"name-of-dep" "version"}]

thheller09:10:38

shadow-cljs npm-deps will attempt to install them but that needs work, I’m not happy with how that works

thheller09:10:42

I’m sort of leaning towards including a package.json in your .jar

thheller09:10:05

but deps.cljs is probably the way to go for now

mhuebert09:10:41

ok, fair enough

mhuebert09:10:39

Ever seen something like this? cljs.core.async is compiling with a trailing js.map:

cljs.core.async.partition_by.cljs$lang$maxFixedArity = 3;


//# sourceMappingURL=cljs.core.async.js.map
js.map
saw it for the first time now in 2.0.13

mhuebert09:10:56

(leading to Uncaught ReferenceError: js is not defined)

thheller09:10:12

maybe 2 shadow-cljs watches running?

thheller09:10:54

no clue otherwise

mhuebert09:10:55

am watching 3 builds at once

mhuebert09:10:04

with one watch command unless there is something running i’m not seeing

mhuebert09:10:51

is there a wiki page or sth where i should make notes of these JS related Q&A’s / stubs for documentation?

mhuebert09:10:08

i’ll just start a page in the shadow-eval repo wiki

thheller10:10:23

yeah I haven’t written down anything yet

thheller10:10:52

haha this externs stuff was waaaaaay easier to implement than I expected

thheller10:10:59

looking good so far

thheller10:10:58

haha it works

thheller10:10:40

that should cover almost all externs … as long as you dont use :resolve :target :global

thheller10:10:52

and some dynamic programming things jquery and others do

thheller10:10:23

what I do is so simple .. for every JS dependency shadow-cljs imports

thheller10:10:40

it collects all assign property names, and creates externs for them

thheller10:10:56

exports.foo = 1; would collect the foo

thheller10:10:19

exports.thing = {a: 1}; would collect thing and a

mhuebert10:10:05

that sounds great, i look forward to trying it

mhuebert10:10:35

you parse the js AST for this?

thheller10:10:08

but I do this anyways to process the require in JS files

thheller10:10:55

quite simple actually

mhuebert10:10:30

working at the right depth in the stack / layer of abstraction can make things so simple

thheller10:10:59

need to work out how to do this for node but for the browser it seems to actually work reasonably well

thheller10:10:33

still doing a few tests to confirm

thheller10:10:57

/* @constructor */ function ShadowJS() {};
ShadowJS.prototype.$$typeof;
ShadowJS.prototype.Children;
ShadowJS.prototype.Component;
ShadowJS.prototype.PureComponent;
ShadowJS.prototype.ReactCurrentOwner;
ShadowJS.prototype.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
ShadowJS.prototype.__self;
ShadowJS.prototype.__source;
ShadowJS.prototype._owner;
ShadowJS.prototype.assign;
ShadowJS.prototype.children;
ShadowJS.prototype.cloneAndReplaceKey;
ShadowJS.prototype.cloneElement;
ShadowJS.prototype.constructor;
ShadowJS.prototype.context;
ShadowJS.prototype.count;
ShadowJS.prototype.createElement;
ShadowJS.prototype.createFactory;
ShadowJS.prototype.current;
ShadowJS.prototype.enqueueForceUpdate;
ShadowJS.prototype.enqueueReplaceState;
ShadowJS.prototype.enqueueSetState;
ShadowJS.prototype.forEach;
ShadowJS.prototype.forceUpdate;
ShadowJS.prototype.framesToPop;
ShadowJS.prototype.func;
ShadowJS.prototype.isMounted;
ShadowJS.prototype.isPureReactComponent;
ShadowJS.prototype.isReactComponent;
ShadowJS.prototype.isValidElement;
ShadowJS.prototype.key;
ShadowJS.prototype.keyPrefix;
ShadowJS.prototype.map;
ShadowJS.prototype.name;
ShadowJS.prototype.only;
ShadowJS.prototype.props;
ShadowJS.prototype.prototype;
ShadowJS.prototype.ref;
ShadowJS.prototype.refs;
ShadowJS.prototype.render;
ShadowJS.prototype.result;
ShadowJS.prototype.setState;
ShadowJS.prototype.thatReturns;
ShadowJS.prototype.thatReturnsArgument;
ShadowJS.prototype.thatReturnsFalse;
ShadowJS.prototype.thatReturnsNull;
ShadowJS.prototype.thatReturnsThis;
ShadowJS.prototype.thatReturnsTrue;
ShadowJS.prototype.toArray;
ShadowJS.prototype.type;
ShadowJS.prototype.unstable_AsyncComponent;
ShadowJS.prototype.unstable_isAsyncReactComponent;
ShadowJS.prototype.updater;
ShadowJS.prototype.version;

thheller10:10:18

doesn’t look all that bad, thats just React v16 and its deps

thheller10:10:12

crap its missing the lifecycle fns 😞

thheller10:10:10

nevermind those are part of react-dom

thheller11:10:07

;; auto
Flushing: base.js (181706 bytes)
Flushing: react.js (111923 bytes)
Flushing: demo.js (195720 bytes)
Flushing: extra.js (136 bytes)
Flushing: worker.js (75 bytes)

;; manual
Flushing: base.js (181233 bytes)
Flushing: react.js (111923 bytes)
Flushing: demo.js (195684 bytes)
Flushing: extra.js (136 bytes)
Flushing: worker.js (75 bytes)

thheller11:10:36

not at all bad, auto is using the generated externs, manual is using the default react externs

thheller11:10:47

pretty much no diff

thheller11:10:39

I call this a success

thheller11:10:23

@mhuebert [email protected] has this enabled by default, if you feel like testing.

thheller11:10:29

I’m gonna verify this on my work project now

mhuebert12:10:48

cool, just in the middle of another thing atm but can try later

mhuebert12:10:02

that is remarkably similar

mhuebert12:10:25

I can’t believe how fast these advanced builds are

mhuebert12:10:58

decided to try importing firebase, eg. yarn install firebase and then ["firebase/app" :as firebase]. it is supposed to work with webpack etc.; I get:

ExceptionInfo: no source by name: node_modules/@firebase/util/dist/cjs/src/constants.ts

mhuebert12:10:51

not sure why it would be looking for constants.ts vs constants.js, which does exist

mhuebert12:10:59

React appears to work now without supplying any externs manually 🙂

mhuebert12:10:14

adding something to :npm-deps in deps.cljs does not appear to have any effect, with or without :lein true

thheller13:10:31

@mhuebert yes :npm-deps is not finished. you can try shadow-cljs npm-deps but it has some issues

thheller13:10:51

until thats sorted out you need to trigger the install manually

mhuebert13:10:20

does shadow look in the node_modules of dependencies?

mhuebert13:10:44

so Maria depends on Commands which has an npm dependency keypress.js

mhuebert13:10:10

does keypress.js need to be in the node_modules of Maria, or can it be in Commands

thheller13:10:31

I don’t understand

mhuebert13:10:09

{:npm-deps {"keypress.js" "<version>"}} is in the deps.cljs of the commands project

mhuebert13:10:30

it is only a transitive dep of Maria.

mhuebert13:10:06

does it somehow need to end up being installed in maria/node_modules or would it also be picked up in commands/node_modules

thheller13:10:32

shadow-cljs ONLY checks <project-root>/node_modules/.... never anywhere else

mhuebert13:10:06

so ultimately shadow-cljs npm-deps could crawl other projects’ deps.cljs and install all the listed :npm-deps into the current project

thheller13:10:18

thats what it does yes

mhuebert13:10:23

makes sense.

thheller13:10:02

looking at the firebase error

thheller13:10:59

its a bit too smart

thheller13:10:46

the firebase files have source maps, which apparently causes it to return constants.ts instead of constants.js when asking which filename it is processing currently

thheller13:10:22

thats awesome and scary 😛

thheller13:10:02

SEVERE: node_modules/@firebase/util/dist/cjs/src/constants.js:26:
Originally at:
node_modules/@firebase/util/dist/cjs/src/constants.ts:26: ERROR - @define variable  assignment must be global

Oct 13, 2017 3:28:12 PM com.google.javascript.jscomp.LoggerErrorManager println
SEVERE: node_modules/@firebase/util/dist/cjs/src/constants.js:30:
Originally at:
node_modules/@firebase/util/dist/cjs/src/constants.ts:31: ERROR - @define variable  assignment must be global

thheller13:10:05

this is the problem though

thheller13:10:59

@define is a closure annotation, will disable that check if possible

thheller13:10:53

can you please check the line count of target/shadow-cljs/builds/browser/release/shadow.js.auto_externs.js?

thheller13:10:09

total curious how many that generates for your project

mhuebert13:10:20

deleted target dir, still seem to be getting the same error with firebase

mhuebert13:10:24

with 2.0.15

mhuebert13:10:34

no source by name: node_modules/@firebase/util/dist/cjs/src/constants.ts
{:name "node_modules/@firebase/util/dist/cjs/src/constants.ts"}
ExceptionInfo: no source by name: node_modules/@firebase/util/dist/cjs/src/constants.ts

mhuebert14:10:26

with

["firebase/app" :as firebase]
["firebase/auth"]

mhuebert14:10:08

I had :lein true and had not updated the shadow-cljs version in project.clj

mhuebert14:10:13

only in package.json

mhuebert14:10:17

trying again

mhuebert14:10:56

it still says 2.0.15 when loading the script, this could be confusing, but maybe there is no way to easily catch if the version is different in project.clj

mhuebert14:10:50

1569 lines in auto_externs

mhuebert14:10:23

this is just the ‘outer’ frame so not so many deps, firebase + react

mhuebert14:10:28

lots of minified stuff in here

mhuebert14:10:36

build went from 400 > 600 kb

thheller14:10:39

wonder where those minified props come from

thheller14:10:19

can you send me the target/shadow-cljs/builds/browser/release/shadow-js/* files zipped or so?

thheller14:10:54

there is probably a closure minified thing in there, want to see if I can somehow detect that

mhuebert14:10:11

could it not be react, minified?

thheller14:10:45

no, JS deps usually do not minify property assigns

thheller14:10:52

only locals

thheller14:10:35

weird, there is only the minified source available for this

thheller14:10:47

but yeah that looks like it was minified by closure

thheller14:10:15

those things will need externs

thheller14:10:22

your build probably gets so large because all the short names are taken

thheller14:10:25

the externs file “claims” all the a, b, c, Aa, Ab, etc names

thheller14:10:51

so probably all vars in your build become 3 letter names instead of 2

thheller14:10:00

that adds up quick

mhuebert14:10:39

yeah exactly

mhuebert14:10:52

it’s not a problem here, since firebase is a google thing they do maintain externs

mhuebert14:10:03

provided externs can be looked up directly from node_modules

thheller14:10:19

yeah but how do I protect the generator from extracting these

thheller14:10:54

only thing I can think of is setting a “cap”

thheller14:10:06

so if a namespace has too many properties you get a warning or something

thheller14:10:11

and then add an exclude option to ignore properties from that file

thheller14:10:41

no normal file should export this many props

mhuebert14:10:21

yeah firebase has often only released minified stuff

thheller14:10:47

or I just ignore the pattern of names

thheller14:10:58

ah better idea

mhuebert14:10:35

is there a way to disable this temporarily

mhuebert14:10:48

i’d like to compare the gzipped sizes of with / without

thheller14:10:53

:js-options {:generate-externs false}

mhuebert14:10:40

sorry. it makes only a tiny difference in file size.

thheller14:10:17

it still seems wrong 🙂

thheller14:10:26

I have a quick fix

thheller14:10:15

(set/superset? properties #{"a" "b" "c" "Aa" "Ab" "Ac"})

thheller14:10:34

if the file contains those properties, I strip out every 1/2 letter property

thheller14:10:56

475 properties for firebase/app + firebase/auth after

thheller14:10:18

so over 1k removed. seems good.

mhuebert14:10:06

so, an example to try

mhuebert14:10:41

in a namespace with this required:

["firebase/app" :as firebase]
["firebase/auth"]
try (js/console.log (.auth firebase))

mhuebert14:10:53

for me it mangled auth

thheller14:10:18

right. nothing I can do about that

thheller14:10:42

the closure minified files use goog.exportSymbol calls to create the names

thheller14:10:18

they look like this Z(a,"EmailAuthProvider",Ig,[]);

thheller14:10:25

that is not a property assign

thheller14:10:34

meh I might scrap this whole idea

thheller14:10:56

CLJS already has a half-way working :infer-externs .. should just use that

mhuebert14:10:24

i think whatever they have + a simple way to add additional properties might be decent

mhuebert14:10:50

this strikes me as extremely hard to get right

thheller14:10:24

the automation part is hard yes, thats why I was surprised why the generation thing worked so well

thheller14:10:50

its still pretty good, just didn’t anticipate this 😛

thheller14:10:24

I’ll look at :infer-externs more, maybe I can combine that in some way

thheller14:10:43

the only part I didn’t like about :infer-externs was that you sometimes have to annotate your code

thheller14:10:57

and often it would complain about things

mhuebert14:10:37

hmm. could it make sense that firebase’s externs don’t work when firebase is required via npm vs. included in a script tag

thheller14:10:45

is the firebase source available at all?

thheller14:10:08

oh you have the firebase externs included and they don’t work?

mhuebert14:10:50

appears that way, checking again

thheller14:10:07

but there is no source in there

thheller14:10:10

only compiled things

thheller14:10:38

oh right .. typescript

thheller14:10:40

nevermind 😉

mhuebert14:10:42

i wouldn’t worry too much about firebase in particular. i just thought it might be a good one to try because google usually annotates everything etc… but i guess that is more relevant for things handled by closure compiler

thheller14:10:24

removing all the short names should fix most issues

mhuebert14:10:23

i guess externs on short names wouldn’t make a big file impact, because you can’t rename these things shorter anyway? its not preventing the closure compiler from reducing the size of $some_long_name$

thheller14:10:24

but instead of turning some_long_name into a its forced to turn it into abc or whatever

thheller14:10:37

2 extra bytes per name is quite a lot

thheller14:10:04

but thats fixed now

thheller14:10:13

did you try with the firebase externs?

thheller14:10:22

if they don’t work thats a problem

mhuebert14:10:57

have to do a bit of code shuffling to try that properly

thheller14:10:18

where are those externs?

mhuebert14:10:39

node_modules/firebase/externs

mhuebert14:10:43

no it works

mhuebert14:10:59

i had to move some initialization code into cljs to test properly

thheller15:10:03

I’ll test a few things with :infer-externs, maybe I can find a solution that is more reliable

thheller15:10:33

should have done that in the first place, just got too excited by this idea of collecting all properties 😛

mhuebert15:10:43

I mean it’s a pretty big win that a library like React ‘just works’ with this generate-externs option

thheller15:10:06

yes but I think :infer-externs can cover this as well

mhuebert15:10:09

and again after testing properly it does not appear to inflate sizes a lot

mhuebert15:10:16

with lots of type hints

thheller15:10:49

I used to be more strict about externs

thheller15:10:03

too obsessed with file sizes over convenience

thheller15:10:30

but should focus more on the user and :externs come up pretty much always

thheller15:10:03

so generated something good, then let user optimize when needed is better

mhuebert15:10:38

with React + Firebase, difference is only 2kb adding :generate-externs in addition to the ‘real’ externs for both libs

thheller15:10:51

thats reasonable

mhuebert15:10:59

generated externs appear to totally solve ‘React’ but fail with firebase due to auth issue mentioned above

mhuebert15:10:18

its a tiny percentage, 601 vs 603 kb

thheller15:10:22

:infer-externs should cover that

thheller15:10:05

if you get a chance you could try master which has the naming fixes

thheller15:10:39

but 2kb is ok enough already, probably doesn’t change much

mhuebert15:10:12

0.3%, negligible

mhuebert15:10:19

but should try it on some more files

mhuebert15:10:47

I agree this is an important thing though, and worth the effort. has been one of the most frustrating aspects of ClojureScript, this gulf between dev and production where suddenly new bugs appear, and extremely slow feedback cycles with advanced compilation. (well: i am very surprised/impressed at how much faster it is with shadow. i thought the slowness was somehow necessary part of the process)

thheller15:10:27

well shadow-cljs caches more aggressively so the only thing you pay for is the actual :advanced compilation

thheller15:10:45

other CLJS tools do not cache at all for :advanced builds, so it always recompiles all CLJS as well

thheller15:10:52

which typically takes longer than the optimize part

thheller16:10:08

geez forgot how complicated this :infer-externs stuff is …

thheller16:10:01

(js/console.log (firebase/auth)) got this part figured out

thheller16:10:15

(js/console.log (.auth firebase)) is also valid but harder to track

mhuebert18:10:02

small thing, if we could make the debugging statements in shadow.cljs.bootstrap.browser optional 🙂

thheller20:10:04

@mhuebert oops yes, forgot to disable them

thheller20:10:32

@jiyinyiyong those things don’t really apply to shadow-cljs 😉