New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add babel transpilation support and phantomjs browser tests (now also testing nunjucks-slim) #1053
Conversation
Another thing to note: I set eslint as the target branch so that the whitespace and linting changes can be reviewed independently of the more substantive changes in this PR. |
Wow, that's a lot of work!
Hm, that solution definitely imposing some issues.
The thing is, not all people would want to use transpiled version, mostly because CommonJS Thus, usually in This also explains why there is no point in adding ES6 version to Now, there is an issue with supporting legacy users. I don't see any other solution except temporarily do movements in proposed way, but to plan within next major version bump to rename
It would be better to use Since built files shouldn't be ignored in Note, that to build the library on the end-user side we need to force install development dependencies, which are completely useless for the end-user. So, that's not a good thing to do.
Wow, there is something really strange happens there. I have a strong feeling that it's possible to make it simpler. Though, I can't tell straight away. Maybe it's just a Mocha ways to do this. I'm usually using Jest, which is basically Jasmine on steroids + jsdom. Due to jsdom it is much easier to tests such things. However, I'm not sure that it will allow testing memory leaks, because the environment is different. |
I'm heading to lunch so I'll reply in more detail to your comments (thanks for that, by the way!), but three quick notes:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, there is another thing, related to bundling. Actually, we have to bundle more states of the lib than we do now.
Fortunately, I've found lightweight and quite self-explaining article regarding this: https://medium.com/@tarkus/how-to-build-and-publish-es6-modules-today-with-babel-and-rollup-4426d9c7ca71
The only thing which should be corrected, that today it is (almost) a standard to use module
instead of jsnext:main
field (some info)
Kinda living resemblance is https://github.com/reactjs/redux repository.
.babelrc
Outdated
], | ||
"presets": [["env", { | ||
"targets": { | ||
"browsers": ["last 2 versions", "safari >= 7", "ie 9"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is better to replace this with single package.json
. property:
"browserslist": ["last 2 versions", "safari >= 7", "ie 9"],
See https://github.com/ai/browserslist
In fact, it is a recommended by developers method.
node_js: | ||
- "7" | ||
- "8" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is unclear why Node 7 no longer tested, as well as 5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node 5 and 7 are no longer maintained by the Node.js Foundation (even-numbered versions are designated for long-term support, but odd-numbered versions are only supported 2 months after a new LTS release comes out. The full details are on the nodejs blog post about the 8.0.0 release.
9 should probably be added though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. Fair enough.
- "4" | ||
install: | ||
- npm install | ||
- npm install codecov |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not install codecov
as part of development dependencies?
package.json
Outdated
"babel-core": "^6.26.0", | ||
"babel-loader": "^7.1.2", | ||
"babel-plugin-istanbul": "^4.1.5", | ||
"babel-plugin-module-resolver": "3.0.0-beta.5", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using custom resolver is close to cardinal sin. It will make code unusable for others, if they, for instance, will decide to require src
directly. Unless it's used for something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely, the aliasing functionality of this plugin is a terrible idea, but in my case I'm using it with webpack to remap certain packages for web (specifically: src/loaders.js
becomes src/web-loaders.js
and src/precompiled-loaders.js
for full and slim builds, respectively), and also to map modules unneeded by the slim build to an empty module (e.g. src/precompile.js
)
This config is specified in scripts/bundle.js and only applies to the browser bundles.
Currently on master this happens in bin/bundle.js, except it uses webpack.NormalModuleReplacementPlugin
instead. I found that using that with babel caused weirdness in the code coverage results, and anyway I like the flexibility of being able to do browser / slim mappings in a callable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using it with webpack to remap certain packages for web (specifically: src/loaders.js becomes src/web-loaders.js and src/precompiled-loaders.js for full and slim builds, respectively),
That's sound like an issue with architecture, and resolver only makes things much less obvious.
Instead, a proper loader should be passed for proper cases at first place. This means that instead of remapping things, few versions of nunjucks/index.js
should exist, which will import and use proper loaders and use or not use precompiler.
Shared functionality can be extracted into a standalone file.
Webpack should build each index.js
version respectively.
There will be no need to remap things and it will become much easier to understand that Nunjucks supplies few different entry points. It will be even easier to tell how those entry points functionality differs just by looking onto imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great idea. For whatever reason using separate entrypoints had not occurred to me, probably because I was thinking about how it used to be done, but it's a much more elegant solution (and would make jsdom-style testing easier too)
@@ -6,19 +6,50 @@ | |||
"dependencies": { | |||
"a-sync-waterfall": "^1.0.0", | |||
"asap": "^2.0.3", | |||
"postinstall-build": "^5.0.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can avoid this dependency for end-user by using prepublishOnly
script, as described in my issue comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's true if someone does a source install, e.g. npm install mozilla/nunjucks
. The purpose of postinstall-build is to make npm install
work with git installs without also having to move your build dependencies from devDependencies
to dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is not da way.
If someone crazy enough to make NPM install directly from Github, he should realize consequences and should invoke building manually. That's is da way.
Adding a ton of dependencies just for building to already bloated node_modules
of any user is a cardinal sin. Besides, a module can be consumed not only by NPM, but also by JSPM, where postbuilding is simply unavailable (probably).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must not be explaining things properly. This 40K dependency (a 9K download from npm) makes it so that the build dependencies are not needed to npm install the package. Failure to do something like this would break npm install mozilla/nunjucks
(or, more likely npm install userwithfork/nunjucks
) which is a pretty common use-case. Otherwise I would need to commit the dist files to source control (bad) or move the build deps from devDependencies
to dependencies
(worse).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I finally got what you're trying to do. I've read postinstall-build
description and got an impression that you want completely move prepublishing build to the end-user. My bad.
Well, I see the point. Not sure that it worth it, but anyway, why not.
"uglifyjs-webpack-plugin": "^1.1.6", | ||
"webpack": "^3.10.0" | ||
}, | ||
"buildDependencies": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something new. What's it used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a feature of postinstall-build. It allows you to specify a subset of your devDependencies
necessary to run the postinstall
hook (when doing a git npm install, for instance).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh my, I already imagine like npm install
in Kotsu adding few other nice minutes of long waiting just while Nunjucks install all those dependencies to build itself...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only happens if the installation is from git (the full command is postinstall-build src
; the argument src
is what changes the behavior: if the src
folder is absent (which, based on your suggestions I will rename dist or lib) then it installs the build dependencies and calls npm run build
, otherwise it does nothing). Does that address your concerns about build duration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I've already replied in #1053 (comment), I've got it totally wrong, so my objection above no longer valid :)
package.json
Outdated
"build": "npm run babel && npm run bundle", | ||
"codecov": "codecov", | ||
"mocha": "mocha -R spec tests", | ||
"instrument": "cross-env NODE_ENV=test scripts/bundle.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be more obvious to call script bundle:test
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe test:bundle
.
Same is for codecov
and mocha
. Probably would be better to group them under test:mocha
and test:coverage
package.json
Outdated
"lint": "eslint .", | ||
"test": "eslint . && istanbul cover ./node_modules/mocha/bin/_mocha -- -R dot tests", | ||
"browserfiles": "./bin/bundle" | ||
"babel": "babel nunjucks --out-dir .", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend to avoid binding to library names and instead call it transpile
Well, we don't have all cases, because there could be private repositories. And even if there are none, it still feels like an ignorance of proper versioning. We're introducing a breaking change, after all. Btw, I guess it is a more convention to build into
We have no choice here. Sooner or later we will need to transition to ES6 modules since it is a standard. I'd say, it's better to do sooner just because it will save us time in the future. Yes, I know that tree shaking isn't beneficial for Nunjucks right now. But it is right now. It might change in future. Nevertheless, even if Nunjucks will never benefit from tree shaking, we're still obligated to use standards, which ES6 modules are.
That's alright. I didn't propose to package non-transpiled version as a main entry point. See my comment #1053 (review)
Well, you can't :) Only one However, I don't see anything criminal it bumping Node version higher. In fact, we're obligated to bump to newer and safer versions. |
I take your point about needing to migrate to ES6 modules eventually, but for the moment I think "later" is more prudent than "sooner." Browsers are beginning to support it (though Firefox notably has the feature hidden behind the I didn't decide against ES modules lightly, I actually went pretty far down the rabbit hole, even replacing webpack and uglify with jspm + systemjs / rollup and had the whole thing working. But in the end it proved to be a lot of trouble for not much gain—at least right now. On the plus side these changes to the build process make a transition to ES modules trivial. One thing I'm not fully decided on is whether to transpile the files individually or whether to use webpack with target: 'node' to create a single file (perhaps |
Since there is no tree shaking, it mostly doesn't make difference. However, it will prevent users from requiring individual parts. If I'm not mistaken, let's say, I need only the parser — requiring parser will also pull whole For instance, lodash provides both versions — and packed into single file Redux provides even more versions:
Source maps shouldn't hurt anyone, I guess. |
That's why as the main entry point still transpiled version distributed. In other cases (like, say, with Webpack or JSPM) users almost always transpiling code anyway, so Node version doesn't mean anything for them. I'm not insisting. It simply doesn't make much difference to argue about it. I'm just saying that it is unavoidable anyway.
Hm, didn't encounter any issues with them. They are mostly just... well... imports and exports. No gains, no loses. Just the way it should be done. Issues are not within ES modules, but CommonJS. They are. But usually, they don't go as far as explaining in docs that for default export should be used |
docs | ||
tests | ||
bench | ||
nunjucks | ||
scripts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting, that instead of maintaining .npmignore
it is easier to specify files
property in package.json
.babelrc
Outdated
"exclude": [ | ||
"es6.map", "es6.set", "es6.function.name", "es6.symbol", | ||
"es6.regexp.split", "es6.regexp.replace", "es6.regexp.match", | ||
"es6.string.repeat", "es6.object.set-prototype-of", "es6.regexp.flags" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what happens here. It seems like .babelrc
config becomes more complicated than it should...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Babel is a bit over-agressive in including builtins, which balloons the size of the bundles. There are trade-offs in life: sometimes you get a more complicated babelrc to get a smaller js bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just say that excluding features this way is slightly dangerous. Using presets is a more accurate approach. But I guess since it is tested on older Node instances, it shouldn't be an issue...
.babelrc
Outdated
"loose": true, | ||
"useBuiltIns": "usage", | ||
"targets": { | ||
"browsers": ["last 2 versions", "safari >= 7", "ie 9"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As already said, it is better to specify this in package.json
browser list property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, there are several of your suggestions I haven't implemented yet. I'm just trying to make progress on this project. I'm not a huge fan of having a giant pull request with so many changes, so I'd like to get this merged into master sooner rather than later. At that point I can tackle the smaller things in more manageable PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
'semi-spacing': ['error', {'before': false, 'after': true}], | ||
'keyword-spacing': ['error', {'before': true, 'after': true}], | ||
"rules": { | ||
// The one assertion of personal preference: no spaces before parentheses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh no, not this again. Accepting common standards is hard, but it requires some courage and patience. It worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have comments explaining why I have disabled each of the rules. I did not do it without a good reason. And this top rule is the only one that I actually changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might find this issue instructive. The people that maintain the airbnb style guide recognize that some rules are more controversial than others and that there are certain cases where disabling specific rules makes more sense than consistency for its own sake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not do it without a good reason. And this top rule is the only one that I actually changed.
I can find a good reason for each rule why not to disable it. It is still a matter of personal preference, which plagues JS community. That's why striker tools, like Standard or Prettier, probably is a better choice. I'm just slightly disappointed that Nunjucks will continue that charade.
Anyway, arguing about codestyle is the same as arguing about tabs vs spaces. I've declared my concerns and they have been heard, I don't have anything meaningful to add. The choice is yours anyway to make.
'sourceType': 'module', | ||
'ecmaVersion': 2017 | ||
'extends': [ | ||
'airbnb-base/legacy', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @noahlange reasonably said here #1052 (comment) taking Airbnb confit isn't a good idea if you want to use ES6+ features in Nunjucks. And since you do, taking an Airbnb config will result in a battle with it. Doesn't sound right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
airbnb-base/legacy is basically the airbnb rules without all the es6 stuff. It seemed like a good fit, it was the closest fit to my own personal preferences.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my own personal preferences
That's the thing which always troubles JS developers. Our own preferences. And so we desecrate the unification. Nevertheless, the project is your, so are the rules.
@@ -54,19 +58,23 @@ | |||
"optionalDependencies": { | |||
"chokidar": "^1.6.0" | |||
}, | |||
"_moduleAliases": { | |||
"babel-register": "@babel/register" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder is it used anywhere? I see all instances pointing to the @babel/register
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll be able to remove it when istanbuljs fully supports babel 7.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, okey.
You're doing a great job, which I and others were too lazy to do for years! Sorry if I'm sounding too harsh at some points. Do not confuse it with lack of appreciation of your work. |
This requires the Symbol.iterator well known symbol and Array.from to coerce them into vanilla arrays.
Add support for ES iterators
Using babel comes at a cost. In order to compensate, several tweaks are made in this commit to reduce the overall bundle size. These include: - Mangling all property names that begin with an underscore - Using simpler functions for Object.keys() and other ES6 features where we know that certain assumptions hold (that, for instance, we don't have to deal with symbols) - Using Obj.extend rather than class syntax when creating the Node classes In order to detect regressions caused by this more aggressive minification, the browser tests now use the minified bundle. The removal of niceties like Object.keys() is temporary until nunjucks v4, which will be distributed with a separate legacy build. The net effect is to decrease nunjucks-slim.min.js by 13K and nunjucks.min.js by 22K.
Having a single test for compiling operators that tests everything from addition and subtraction to python-style ternary operators support isn't very useful when something breaks.
I'm going to open a separate ticket with this information, but thought it might make sense to share here first the plans for this PR and a proposed roadmap for nunjucks: In this PR I've opted to be extremely conservative. I suspect that's mostly the cause of the decisions that @ArmorDarks has been (rightly) critical of. There are trade-offs I have had to make to ensure that I can release this as nunjucks 3.1.0 and be fairly certain that it won't break anyone's code. The idea is that I can immediately start on nunjucks 4 and have the 3.x branch be sufficiently similar that it's not too burdensome to backport (non-breaking) bug fixes. In nunjucks 4 (the alpha release of which will follow quickly on the heels of merging this in) a few things will change:
All of this will also set the groundwork for more ambitious goals for nunjucks. I would very much like to make nunjucks able to support plugins. The I would also like to make nunjucks "slim" truly slim. Perhaps allowing the option of inlining the runtime functions and make precompiled templates able to run without needing to include the nunjucks library. This is just an idea, but if there is interest in it then I will build it. |
Sounds awesome 👍 |
Fix pull request id in CHANGELOG.md
Summary
An overhaul of the bundle script and test runner, adding support for babel transpilation and phantomjs browser tests (the latter are instrumented with babel-plugin-istanbul and contribute to the coverage report). I'd particularly be interested in soliciting feedback from @ArmorDarks and @noahlange (as well as @carljm if he would like to weigh in).
Proposed change:
Bundling / build process changes:
babel-loader
and uses the babel "env" preset to ensure the code runs on node > 4 and modern(ish) browsers. phantomjs is a decent target for the latter since it lacks support for most es6 syntax and features (this should include an update toCONTRIBUTING.md
to bring it into alignment with the FAQ, which says we support whatever versions are currently supported by the Node.js Foundation Release Working Group)./index.js
and/src/*.js
to the/nunjucks
subdirectory of the repo. During thenpm run build
process the original files are generated bybabel-cli
. This is to ensure that it doesn't introduce regressions if some developer has a deep import into nunjucks (e.g.var installJinjaCompat = require('nunjucks/src/jinja-compat')
). The original un-transpiled source files are in.npmignore
and the transpiled files are in.gitignore
.npm run build
executes if the dist files are missing. This removes the need to check the dist/browser files into git./browser/
files are removed from the repo and are now in.gitignore
.Test changes:
/tests/browser/slim.html
. When run throughnpm test
it first precompiles the templates in/tests/templates/
.npm test
now also runs the mocha browser tests with phantomjs (though the mocha browser tests existed before this PR, they were not run in CI). Before and after executing phantomjs it starts and stops a simple static http server; the code for all of this can be found in/scripts/
/tests/browser
, but are included from the repository'snode_modules
directoryCloses #1019 (had to be fixed for the slim tests to pass)
Checklist
CONTRIBUTING.md