diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c03c7d6b8..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: 2 - -jobs: - test app: - docker: - - image: meteor/circleci:android-28-node-12 - steps: - - checkout - - run: - name: "Install Meteor" - command: | - curl https://install.meteor.com/ | sh - - run: - name: "Run Tests" - command: | - cd test-app - pwd - ./ci.sh - -workflows: - version: 2 - Blaze Tests: - jobs: - - test app diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..b0e4824c7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: blaze +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..fcb49681d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md @@ -0,0 +1,21 @@ +--- +name: 🐛 Bug +about: Create a report to help us improve Blaze +title: '' +labels: '' +assignees: '' +--- + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..3c04df8e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,19 @@ +--- +blank_issues_enabled: false +contact_links: + - + about: Ask a question or for help on the Meteor forums + name: ❓ Question + url: https://forums.meteor.com/ + - + about: Chat on our community Slack + name: 🗯 Chat + url: https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc + - + about: The official Blaze website + name: ℹ️ Website + url: https://www.blazejs.org/ + - + about: Blaze documentation + name: 📜 Documentation + url: https://www.blazejs.org/guide/introduction.html diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..27b3f5f82 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +<-- +First, 🌠 thank you 🌠 for taking the time to consider a contribution to Blaze! + +Here are some important details to follow: + +* ⏰ Your time is important + To save your precious time, if the contribution you are making will take more + than an hour, please make sure it has been discussed in an issue first. + This is especially true for feature requests! +* 🕷 Bug fixes + These can be created and discussed in this repository. When fixing a bug, + please _try_ to add a test which verifies the fix. If you cannot, you should + still submit the PR but we may still ask you (and help you!) to create a test. +* 📖 Contribution guidelines + Always follow https://github.com/meteor/blaze/blob/master/CONTRIBUTING.md + when submitting a pull request. Make sure existing tests still pass, and add + tests for all new behavior. +* ✏️ Explain your pull request + Describe the big picture of your changes here to communicate to what your + pull request is meant to accomplish. Provide 🔗 links 🔗 to associated issues! + +We hope you will find this to be a positive experience! Open source contribution can be intimidating and we hope to alleviate that pain as much as possible. Without following these guidelines, you may be missing context that can help you succeed with your contribution, which is why we encourage discussion first. Ultimately, there is no guarantee that we will be able to merge your pull-request, but by following these guidelines we can try to avoid disappointment. +--> \ No newline at end of file diff --git a/.github/workflows/blaze-tests.yml b/.github/workflows/blaze-tests.yml new file mode 100644 index 000000000..0ed61e15f --- /dev/null +++ b/.github/workflows/blaze-tests.yml @@ -0,0 +1,80 @@ +# This workflow runs tests for the app using a similar setup as the CircleCI config. +name: Blaze Tests + +# Trigger on any push or pull request +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + test-app: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node.js environment + uses: actions/setup-node@v4.2.0 + with: + node-version: 22.15.0 + + - name: Install Meteor + run: | + npx meteor@latest + echo "$HOME/.meteor" >> $GITHUB_PATH + + - name: Link packages directory + run: ln -sfn ../packages ./packages + working-directory: test-app + + - name: Meteor npm install + run: meteor npm install + working-directory: test-app + + - name: Start meteor test-packages (background) + run: | + export URL='http://localhost:4096/' + meteor test-packages --driver-package test-in-console -p 4096 --exclude "${TEST_PACKAGES_EXCLUDE:-}" > /tmp/meteor_test_output.log 2>&1 & + echo $! > /tmp/meteor_test_pid + working-directory: test-app + + - name: Wait for test-in-console to be ready + run: | + for i in {1..60}; do + if grep -q 'test-in-console listening' /tmp/meteor_test_output.log; then + echo "test-in-console is ready." + break + fi + echo "Waiting for test-in-console... attempt $i" + sleep 1 + done + # Fail if the service didn't start + if ! grep -q 'test-in-console listening' /tmp/meteor_test_output.log; then + echo "test-in-console did not start in time." + cat /tmp/meteor_test_output.log # Print the log for debugging + exit 1 + fi + shell: bash + working-directory: test-app + + - name: Run puppeteerRunner.js + run: meteor node puppeteerRunner.js + env: + URL: http://localhost:4096/ + working-directory: test-app + + - name: Kill meteor test-packages process + if: always() + run: | + if [ -f /tmp/meteor_test_pid ]; then + pkill -TERM -P $(cat /tmp/meteor_test_pid) + fi + shell: bash + working-directory: test-app \ No newline at end of file diff --git a/.gitignore b/.gitignore index c696cfbbf..95babbe70 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ db.json test-app/packages node_modules package-lock.json +.env \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..f117f1982 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,69 @@ +# Blaze Project Code of Conduct + +### Community and Diversity + +We want to build a productive, happy and agile community that welcomes new ideas, constantly looks for areas to improve, and fosters collaboration. + +The project gains strength from a diversity of backgrounds and perspectives in our contributor community, and we actively seek participation from those who enhance it. This code of conduct exists to lay some ground rules that ensure we can collaborate and communicate effectively, despite our diversity. The code applies equally to founders, team members and those seeking help and guidance. + +### Using This Code + +This isn’t an exhaustive list of things that you can’t do. Rather, it’s a guide for participation in the community that outlines how each of us can work to keep Blaze a positive, successful, and growing project. + +This code of conduct applies to all spaces managed by the Blaze project or company. This includes Slack, GitHub issues, and any other forums created by the Blaze team which the community uses for communication. Breaches of this code outside these spaces may affect a person's ability to participate within them. We expect it to be honored by everyone who represents or participates in the project, whether officially or informally. + +If you believe someone is violating the code of conduct, please report it by emailing [community@meteor.com](mailto:community@meteor.com). + +### We Strive To: + +- **Be open, patient, and welcoming** + + Members of this community are open to collaboration, whether it's on PRs, issues, or problems. We're receptive to constructive comment and criticism, as we value what the experiences and skill sets of contributors bring to the project. We're accepting of all who wish to get involved, and find ways for anyone to participate in a way that best matches their strengths. + +- **Be considerate** + + We are considerate of our peers: other Blaze users and contributors. We’re thoughtful when addressing others’ efforts, keeping in mind that work is often undertaken for the benefit of the community. We also value others’ time and appreciate that not every issue or comment will be responded to immediately. We strive to be mindful in our communications, whether in person or online, and we're tactful when approaching views that are different from our own. + +- **Be respectful** + + As a community of professionals, we are professional in our handling of disagreements, and don’t allow frustration to turn into a personal attack. We work together to resolve conflict, assume good intentions and do our best to act in an empathic fashion. + + We do not tolerate harassment or exclusionary behavior. This includes, but is not limited to: + - Violent threats or language directed against another person. + - Discriminatory jokes and language. + - Posting sexually explicit or sexualized content. + - Posting content depicting or encouraging violence. + - Posting (or threatening to post) other people's personally identifying information ("doxing"). + - Personal insults, especially those using racist or sexist terms. + - Unwelcome sexual attention. + - Advocating for, or encouraging, any of the above behavior. + - Repeated harassment of others. In general, if someone asks you to stop, then stop. + +- **Take responsibility for our words and our actions** + + We can all make mistakes; when we do, we take responsibility for them. If someone has been harmed or offended, we listen carefully and respectfully. We are also considerate of others’ attempts to amend their mistakes. + +- **Be collaborative** + + The work we produce is (and is part of) an ecosystem containing several parallel efforts working towards a similar goal. Collaboration between teams and individuals that each have their own goal and vision is essential to reduce redundancy and improve the quality of our work. + + Internally and externally, we celebrate good collaboration. Wherever possible, we work closely with upstream projects and others in the free software community to coordinate our efforts. We prefer to work transparently and involve interested parties as early as possible. + +- **Ask for help when in doubt** + + Nobody is expected to be perfect in this community. Asking questions early avoids many problems later, so questions are encouraged, though they may be directed to the appropriate forum. Those who are asked should be responsive and helpful. + +- **Take initiative** + + We encourage new participants to feel empowered to lead, to take action, and to experiment when they feel innovation could improve the project. If we have an idea for a new tool, or how an existing tool can be improved, we speak up and take ownership of that work when possible. + +### Attribution + +Sections of this Code of Conduct were inspired in by the following Codes from other open source projects and resources we admire: + +- [The Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) +- [Python](https://www.python.org/psf/codeofconduct/) +- [Ubuntu](https://ubuntu.com/community/code-of-conduct) +- [Django](https://www.djangoproject.com/conduct/) + +*This Blaze Code of Conduct is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) license. This Code was last updated on August 28, 2017.* diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 000000000..46bcff607 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,39 @@ +# How to Deploy the Packages + +## Prerequisites +- Ensure you have Meteor installed and are logged in to your Meteor account + - If you're running a checkout, make sure you're in the right updated branch +- Make sure to update the version numbers in each package's `package.js` file before publishing + +## Publishing Packages + +### Automatic Publishing (Recommended) +Run the `publish-all.sh` script from the root directory: +```bash +./publish-all.sh +``` + +### Manual Publishing +If you need to publish specific packages only: +1. Remove unwanted packages from `publish-all.sh`, or +2. Manually publish individual packages using: +```bash +cd packages/ +meteor publish +``` + +If you change the `publish-all.sh` script removing packages, **do not commit the changes**. + +## Package Dependencies +The publish order is critical due to package dependencies. The current sequence publishes "leaf" packages first (those with fewer dependencies), followed by packages that depend on them: + +``` +1. htmljs 7. observe-sequence 13. templating +2. html-tools 8. blaze 14. spacebars-tests +3. blaze-tools 9. spacebars 15. blaze-html-templates +4. spacebars-compiler 10. templating-compiler 16. ui +5. templating-tools 11. templating-runtime +6. caching-html-compiler 12. blaze-hot +``` + +> ⚠️ **IMPORTANT**: Maintaining this order is crucial for successful deployment. Do not modify the sequence unless you fully understand the dependency chain. diff --git a/HISTORY.md b/HISTORY.md index 9b119c5d5..21652eee4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,58 @@ +## v3.0.2, 2025-02-04 + +### Highlights +* [475](https://github.com/meteor/blaze/pull/475) Enhanced error context for exceptions in helper functions. + +## v3.0.1, 2024-11-14 + +### Highlights / Potentially breaking changes +* [471](https://github.com/meteor/blaze/pull/471) Added support for Promises in `#with` + +## v3.0.0, 2024-07-16 + +### Highlights / Potentially breaking changes +* [#373](https://github.com/meteor/blaze/pull/373) Remove fibers from codebase +* [#378](https://github.com/meteor/blaze/pull/378) [spacebars-compiler] Update uglify-js to 3.16.1 +* [#351](https://github.com/meteor/blaze/pull/351) Eliminate whitespace in Template.dynamic +* [#334](https://github.com/meteor/blaze/pull/334) Faster fragnent parsing by retaining a reference to the current document context +* All packages were bumped to be compatible with Meteor 3.0 + + +## v2.9.0 2024-Mar-14 + +* [#460](https://github.com/meteor/blaze/pull/460) Implemented async dynamic attributes. +* [#458](https://github.com/meteor/blaze/pull/458) Blaze._expandAttributes returns empty object, if null. + + + +## v2.8.0 2023-Dec-28 + +* [#431](https://github.com/meteor/blaze/pull/431) Depracate Ui package. +* [#431](https://github.com/meteor/blaze/pull/432) Bump blaze hot dependencies. +* [#428](https://github.com/meteor/blaze/pull/428) Implemented async attributes and content. +* [#426](https://github.com/meteor/blaze/pull/426) Fix observe-squence has-implementation, close to underscore. +* [#434](https://github.com/meteor/blaze/pull/434) Update templating deps. +* [#435](https://github.com/meteor/blaze/pull/435) Updating dependencies for templating-compiler package. +* [#433](https://github.com/meteor/blaze/pull/433) Update caching-html-compiler. + +## v2.7.1, 2023-May-26 + +* [#413](https://github.com/meteor/blaze/pull/418) Fix reactivity for non-primitives. + + +## v2.7.0, 2023-May-23 + +* [#413](https://github.com/meteor/blaze/pull/413) Added support for Promises in Spacebars.call and Spacebars.dot. +* [#412](https://github.com/meteor/blaze/pull/412) Implemented async bindings in #let. +* + +## v2.6.2, 2023-April-21 + +* [#403](https://github.com/meteor/blaze/pull/403) Add TS types to core +* [#405](https://github.com/meteor/blaze/pull/406) Stop establishing unnecessary reactive dependencies +* [#410](https://github.com/meteor/blaze/pull/410) Fixes for legacy clients + + ## v2.6.1, 2022-July-25 * [#370](https://github.com/meteor/blaze/pull/370) `templating-runtime@1.6.1`, returned the `Template.__define__` with warning message @@ -47,14 +102,14 @@ * [#321](https://github.com/meteor/blaze/pull/321) Just source code modernisation, making it easier to read. Shouldn't change any API's; except may need explicit import if other packages are using directly. * [#324](https://github.com/meteor/blaze/pull/324) Add a whitespace="strip" option to templates, which removes any whitespace that crosses newlines. - + * [#276](https://github.com/meteor/blaze/pull/276) [HTML.isArray](https://github.com/brucejo75/blaze/blob/release-2.4/packages/htmljs/README.md#htmlisarrayx) works across iFrames. This supports running blaze in sandboxed iFrames. ## v2.3.4, 2019-Dec-13 * jquery 3 support [#299](https://github.com/meteor/blaze/pull/299) - + ## v2.3.2, 2017-Mar-21 * Made beautification of compiled spacebars code happen only on the server. diff --git a/README.md b/README.md index 6a70f64ff..77940cf67 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Support us with a monthly donation and help us continue our activities. [Become ## Sponsors -Become a sponsor and get your logo on our README on Github with a link to your site. [Become a sponsor](https://opencollective.com/blaze#sponsor). +Become a sponsor and get your logo on our README on GitHub with a link to your site. [Become a sponsor](https://opencollective.com/blaze#sponsor). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..ece3d1e16 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,44 @@ +# Security Policy + +## Supported Versions + +| Version | Support Status +| ------- | -------------- +| 3.x.y | ✅ all security issues +| >=2.5 | ✅ all security issues +| <2.5 | 🚧 only critical security issues +| <= 2 | ❌ no longer supportted + +## Reporting a Vulnerability + +Report security bugs to security@meteor.com. + +Your report will be acknowledged within 2 work days, and you'll receive a more +detailed response to your report within 6 work days indicating the next steps in +handling your submission. + +After the initial reply to your report, the security team will endeavor to keep +you informed of the progress being made towards a fix and full announcement, +and may ask for additional information or guidance surrounding the reported +issue. + +We don't have any bounty program. + +## Reporting a security bug in a third party module + +Security bugs in third party modules should be reported to their respective +maintainers. + +Thank you for improving the security of Blaze and its ecosystem. Your efforts +and responsible disclosure are greatly appreciated and will be acknowledged. + +## Disclosure policy + +Here is the security disclosure policy for Blaze + +* The security report is received and is assigned a primary handler. This + person will coordinate the fix and release process. The problem is confirmed + and a list of all affected versions is determined. Code is audited to find + any potential similar problems. Fixes are prepared for all releases which are + still under maintenance. These fixes are not committed to the public + repository but rather held locally pending the announcement. diff --git a/packages/blaze-hot/.versions b/packages/blaze-hot/.versions index 75d2ad3fe..37915deab 100644 --- a/packages/blaze-hot/.versions +++ b/packages/blaze-hot/.versions @@ -1,39 +1,38 @@ -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -blaze@2.5.0 -blaze-hot@1.1.0 -blaze-tools@1.1.0 -caching-compiler@1.2.2 -caching-html-compiler@1.2.0 -check@1.3.1 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -ejson@1.1.1 -fetch@0.1.1 -html-tools@1.1.0 -htmljs@1.1.0 -id-map@1.1.0 -inter-process-messaging@0.1.1 -meteor@1.9.3 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo-id@1.0.7 -observe-sequence@1.0.16 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -reactive-var@1.0.11 -spacebars@1.1.0 -spacebars-compiler@1.2.0 -templating-compiler@1.4.0 -templating-runtime@1.4.0 -templating-tools@1.2.0 -tracker@1.2.0 -underscore@1.0.10 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +blaze@3.0.0 +blaze-hot@2.0.0 +blaze-tools@2.0.0 +caching-compiler@2.0.0 +caching-html-compiler@2.0.0 +check@1.4.2 +core-runtime@1.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +fetch@0.1.5 +html-tools@2.0.0 +htmljs@2.0.1 +inter-process-messaging@0.1.2 +meteor@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo-id@1.0.9 +observe-sequence@2.0.0 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reactive-var@1.0.13 +spacebars@2.0.0 +spacebars-compiler@2.0.0 +templating-compiler@2.0.0 +templating-runtime@2.0.0 +templating-tools@2.0.0 +tracker@1.3.4 diff --git a/packages/blaze-hot/hot.js b/packages/blaze-hot/hot.js index 5035e73ce..453980544 100644 --- a/packages/blaze-hot/hot.js +++ b/packages/blaze-hot/hot.js @@ -11,7 +11,7 @@ function patchTemplate(Template) { Template.registerHelper = function (name, func) { func[SourceModule] = currentModule.id; oldRegisterHelper(name, func); - } + }; const oldOnCreated = Template.prototype.onCreated; Template.prototype.onCreated = function (cb) { @@ -20,7 +20,7 @@ function patchTemplate(Template) { } return oldOnCreated.call(this, cb); - } + }; const oldOnRendered = Template.prototype.onRendered; Template.prototype.onRendered = function (cb) { @@ -29,7 +29,7 @@ function patchTemplate(Template) { } return oldOnRendered.call(this, cb); - } + }; const oldOnDestroyed = Template.prototype.onDestroyed; Template.prototype.onDestroyed = function (cb) { @@ -38,12 +38,12 @@ function patchTemplate(Template) { } return oldOnDestroyed.call(this, cb); - } + }; const oldHelpers = Template.prototype.helpers; Template.prototype.helpers = function (dict) { if (typeof dict === 'object') { - for (var k in dict) { + for (let k in dict) { if (dict[k]) { dict[k][SourceModule] = currentModule.id; } @@ -51,18 +51,18 @@ function patchTemplate(Template) { } return oldHelpers.call(this, dict); - } + }; const oldEvents = Template.prototype.events; Template.prototype.events = function (eventMap) { const result = oldEvents.call(this, eventMap); this.__eventMaps[this.__eventMaps.length - 1][SourceModule] = currentModule.id; return result; - } + }; } function cleanTemplate(template, moduleId) { - let usedModule = false + let usedModule = false; if (!template || !Blaze.isTemplate(template)) { return usedModule; } @@ -71,7 +71,7 @@ function cleanTemplate(template, moduleId) { for (let i = array.length - 1; i >= 0; i--) { let item = array[i]; if (item && item[SourceModule] === moduleId) { - usedModule = true + usedModule = true; array.splice(i, 1); } } @@ -84,12 +84,12 @@ function cleanTemplate(template, moduleId) { Object.keys(template.__helpers).forEach(key => { if (template.__helpers[key] && template.__helpers[key][SourceModule] === moduleId) { - usedModule = true + usedModule = true; delete template.__helpers[key]; } }); - return usedModule + return usedModule; } function shouldAccept(module) { @@ -120,7 +120,7 @@ if (module.hot) { module.hot.accept(); module.hot.dispose(() => { Object.keys(Templates).forEach(templateName => { - let template = Templates[templateName] + let template = Templates[templateName]; let usedByModule = cleanTemplate(template, module.id); if (usedByModule) { Template._applyHmrChanges(templateName); @@ -134,7 +134,7 @@ if (module.hot) { }); }); } - currentModule = previousModule + currentModule = previousModule; } }); } diff --git a/packages/blaze-hot/package.js b/packages/blaze-hot/package.js index f4cad2c35..a9b4528d3 100644 --- a/packages/blaze-hot/package.js +++ b/packages/blaze-hot/package.js @@ -1,18 +1,18 @@ Package.describe({ name: 'blaze-hot', summary: "Update files using Blaze's API with HMR", - version: '1.1.1', + version: '2.0.0', git: 'https://github.com/meteor/blaze.git', documentation: null, debugOnly: true }); Package.onUse(function (api) { - api.use('modules@0.16.0'); - api.use('ecmascript@0.15.1'); - api.use('blaze@2.6.0'); - api.use('templating-runtime@1.6.0'); - api.use('hot-module-replacement@0.2.0', { weak: true }); + api.use('modules@0.20.1'); + api.use('ecmascript@0.16.9'); + api.use('blaze@3.0.0'); + api.use('templating-runtime@2.0.0'); + api.use('hot-module-replacement@0.5.4', { weak: true }); api.addFiles('hot.js', 'client'); api.addFiles('update-templates.js', 'client'); diff --git a/packages/blaze-hot/update-templates.js b/packages/blaze-hot/update-templates.js index caef9fb02..57a0757ca 100644 --- a/packages/blaze-hot/update-templates.js +++ b/packages/blaze-hot/update-templates.js @@ -33,7 +33,7 @@ Template.prototype.constructView = function () { } return view; -} +}; let updateRootViews = Template._applyHmrChanges; @@ -59,8 +59,8 @@ Template._applyHmrChanges = function (templateName = UpdateAll) { } timeout = setTimeout(() => { - for (var i = 0; i < Template.__pendingReplacement.length; i++) { - delete Template[Template.__pendingReplacement[i]] + for (let i = 0; i < Template.__pendingReplacement.length; i++) { + delete Template[Template.__pendingReplacement[i]]; } Template.__pendingReplacement = []; @@ -161,4 +161,4 @@ Template._applyHmrChanges = function (templateName = UpdateAll) { } }); }); -} +}; diff --git a/packages/blaze-html-templates/.versions b/packages/blaze-html-templates/.versions index 66c992e12..d8638fb64 100644 --- a/packages/blaze-html-templates/.versions +++ b/packages/blaze-html-templates/.versions @@ -1,41 +1,39 @@ -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -blaze@2.5.0 -blaze-html-templates@1.2.1 -blaze-tools@1.1.0 -caching-compiler@1.2.2 -caching-html-compiler@1.2.0 -check@1.3.1 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -ejson@1.1.1 -fetch@0.1.1 -html-tools@1.1.0 -htmljs@1.1.0 -id-map@1.1.0 -inter-process-messaging@0.1.1 -meteor@1.9.3 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo-id@1.0.7 -observe-sequence@1.0.16 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -reactive-var@1.0.11 -spacebars@1.2.0 -spacebars-compiler@1.2.0 -templating@1.4.1 -templating-compiler@1.4.1 -templating-runtime@1.5.0 -templating-tools@1.2.0 -tracker@1.2.0 -ui@1.0.13 -underscore@1.0.10 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +blaze@3.0.0 +blaze-html-templates@3.0.0 +blaze-tools@2.0.0 +caching-compiler@2.0.0 +caching-html-compiler@2.0.0 +check@1.4.2 +core-runtime@1.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +fetch@0.1.5 +html-tools@2.0.0 +htmljs@2.0.1 +inter-process-messaging@0.1.2 +meteor@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo-id@1.0.9 +observe-sequence@2.0.0 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reactive-var@1.0.13 +spacebars@2.0.0 +spacebars-compiler@2.0.0 +templating@1.4.4 +templating-compiler@2.0.0 +templating-runtime@2.0.0 +templating-tools@2.0.0 +tracker@1.3.4 diff --git a/packages/blaze-html-templates/package.js b/packages/blaze-html-templates/package.js index 99341411d..b34436f27 100644 --- a/packages/blaze-html-templates/package.js +++ b/packages/blaze-html-templates/package.js @@ -1,16 +1,16 @@ Package.describe({ name: 'blaze-html-templates', summary: "Compile HTML templates into reactive UI with Meteor Blaze", - version: '2.0.0', + version: '3.0.0', git: 'https://github.com/meteor/blaze.git' }); Package.onUse(function(api) { api.imply([ // A library for reactive user interfaces - 'blaze@2.5.0', + 'blaze@3.0.0', // Compile .html files into Blaze reactive views - 'templating@1.4.1' + 'templating@1.4.4' ]); }); diff --git a/packages/blaze-tools/.versions b/packages/blaze-tools/.versions index 986ec71a4..ce2d115ff 100644 --- a/packages/blaze-tools/.versions +++ b/packages/blaze-tools/.versions @@ -1,51 +1,54 @@ -allow-deny@1.1.0 -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -binary-heap@1.0.11 -blaze-tools@1.1.2 -boilerplate-generator@1.7.1 -callback-hook@1.3.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.4.0 -ddp-common@1.4.0 -ddp-server@2.3.2 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -ejson@1.1.1 -fetch@0.1.1 -geojson-utils@1.0.10 -html-tools@1.1.1 -htmljs@1.1.0 -id-map@1.1.0 -inter-process-messaging@0.1.1 -local-test:blaze-tools@1.1.2 -logging@1.2.0 -meteor@1.9.3 -minimongo@1.6.2 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo@1.11.0 -mongo-decimal@0.1.2 -mongo-dev-server@1.1.0 -mongo-id@1.0.7 -npm-mongo@3.9.0 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.0 -socket-stream-client@0.3.1 -tinytest@1.1.0 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.10.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +blaze-tools@2.0.0 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.2 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.0 +ddp-common@1.4.3 +ddp-server@3.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +html-tools@2.0.0 +htmljs@2.0.1 +id-map@1.2.0 +inter-process-messaging@0.1.2 +local-test:blaze-tools@2.0.0 +logging@1.3.5 +meteor@2.0.0 +minimongo@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo@2.0.0 +mongo-decimal@0.1.4-beta300.7 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.3 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +socket-stream-client@0.5.3 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.0 +webapp-hashing@1.1.2 diff --git a/packages/blaze-tools/package.js b/packages/blaze-tools/package.js index 7e9722bfd..460d55116 100644 --- a/packages/blaze-tools/package.js +++ b/packages/blaze-tools/package.js @@ -1,24 +1,24 @@ Package.describe({ name: 'blaze-tools', summary: "Compile-time tools for Blaze", - version: '1.1.3', + version: '2.0.0', git: 'https://github.com/meteor/blaze.git' }); Package.onUse(function (api) { - api.use('ecmascript@0.15.1'); - api.use('htmljs@1.1.1'); + api.use('ecmascript@0.16.9'); + api.use('htmljs@2.0.1'); api.export('BlazeTools'); api.mainModule('preamble.js'); }); Package.onTest(function (api) { - api.use('tinytest@1.1.0'); + api.use('tinytest'); api.use('ecmascript'); api.use('blaze-tools'); - api.use('html-tools@1.1.3'); + api.use('html-tools@2.0.0'); api.addFiles([ 'token_tests.js' diff --git a/packages/blaze/.npm/package/npm-shrinkwrap.json b/packages/blaze/.npm/package/npm-shrinkwrap.json index 7863d90bf..9ffecbcbb 100644 --- a/packages/blaze/.npm/package/npm-shrinkwrap.json +++ b/packages/blaze/.npm/package/npm-shrinkwrap.json @@ -1,15 +1,15 @@ { - "lockfileVersion": 1, + "lockfileVersion": 4, "dependencies": { "lodash.has": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" }, "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" }, "lodash.isfunction": { "version": "3.0.9", @@ -19,7 +19,7 @@ "lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==" } } } diff --git a/packages/blaze/.versions b/packages/blaze/.versions index 4d9066379..e51307103 100644 --- a/packages/blaze/.versions +++ b/packages/blaze/.versions @@ -1,64 +1,67 @@ -allow-deny@1.1.1 -babel-compiler@7.9.0 -babel-runtime@1.5.1 -base64@1.0.12 -binary-heap@1.0.11 -blaze@2.6.1 -blaze-tools@1.1.3 -boilerplate-generator@1.7.1 -caching-compiler@1.2.2 -caching-html-compiler@1.1.2 -callback-hook@1.4.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.5.0 -ddp-common@1.4.0 -ddp-server@2.5.0 -diff-sequence@1.1.1 -dynamic-import@0.7.2 -ecmascript@0.16.2 -ecmascript-runtime@0.8.0 -ecmascript-runtime-client@0.12.1 -ecmascript-runtime-server@0.11.0 -ejson@1.1.2 -fetch@0.1.1 -geojson-utils@1.0.10 -html-tools@1.1.3 -htmljs@1.1.1 -id-map@1.1.1 -inter-process-messaging@0.1.1 -jquery@1.11.10 -local-test:blaze@2.6.1 -logging@1.3.1 -meteor@1.10.0 -minimongo@1.8.0 -modern-browsers@0.1.8 -modules@0.18.0 -modules-runtime@0.13.0 -mongo@1.15.0 -mongo-decimal@0.1.3 -mongo-dev-server@1.1.0 -mongo-id@1.0.8 -npm-mongo@4.3.1 -observe-sequence@1.0.16 -ordered-dict@1.1.0 -promise@0.12.0 -random@1.2.0 -react-fast-refresh@0.2.3 -reactive-var@1.0.11 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.1 -socket-stream-client@0.5.0 -spacebars@1.0.15 -spacebars-compiler@1.1.2 -templating@1.3.2 -templating-compiler@1.3.2 -templating-runtime@1.3.2 -templating-tools@1.1.2 -test-helpers@1.3.0 -tinytest@1.2.1 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.13.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.1 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +blaze@3.0.1 +blaze-tools@2.0.0 +boilerplate-generator@2.0.0 +caching-compiler@2.0.1 +caching-html-compiler@2.0.0 +callback-hook@1.6.0 +check@1.4.4 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.2 +ddp-common@1.4.4 +ddp-server@3.0.2 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.3 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +html-tools@2.0.0 +htmljs@2.0.1 +id-map@1.2.0 +inter-process-messaging@0.1.2 +jquery@3.0.2 +local-test:blaze@3.0.1 +logging@1.3.5 +meteor@2.0.1 +minimongo@2.0.1 +modern-browsers@0.1.11 +modules@0.20.2 +modules-runtime@0.13.2 +mongo@2.0.2 +mongo-decimal@0.1.5 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.4 +observe-sequence@2.0.0 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reactive-var@1.0.13 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +socket-stream-client@0.5.3 +spacebars@2.0.0 +spacebars-compiler@2.0.0 +templating@1.4.4 +templating-compiler@2.0.0 +templating-runtime@2.0.0 +templating-tools@2.0.0 +test-helpers@2.0.1 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.3 +webapp-hashing@1.1.2 diff --git a/packages/blaze/attrs.js b/packages/blaze/attrs.js index cbc112cea..50b5162ae 100644 --- a/packages/blaze/attrs.js +++ b/packages/blaze/attrs.js @@ -1,6 +1,7 @@ import has from 'lodash.has'; +import { OrderedDict } from 'meteor/ordered-dict'; -var jsUrlsAllowed = false; +let jsUrlsAllowed = false; Blaze._allowJavascriptUrls = function () { jsUrlsAllowed = true; }; @@ -47,8 +48,8 @@ AttributeHandler.prototype.update = function (element, oldValue, value) { }; AttributeHandler.extend = function (options) { - var curType = this; - var subType = function AttributeHandlerSubtype(/*arguments*/) { + const curType = this; + const subType = function AttributeHandlerSubtype(/*arguments*/) { AttributeHandler.apply(this, arguments); }; subType.prototype = new curType; @@ -73,13 +74,13 @@ Blaze._DiffingAttributeHandler = AttributeHandler.extend({ if (!this.getCurrentValue || !this.setValue || !this.parseValue || !this.joinValues) throw new Error("Missing methods in subclass of 'DiffingAttributeHandler'"); - var oldAttrsMap = oldValue ? this.parseValue(oldValue) : new OrderedDict(); - var attrsMap = value ? this.parseValue(value) : new OrderedDict(); + const oldAttrsMap = oldValue ? this.parseValue(oldValue) : new OrderedDict(); + const attrsMap = value ? this.parseValue(value) : new OrderedDict(); // the current attributes on the element, which we will mutate. - var currentAttrString = this.getCurrentValue(element); - var currentAttrsMap = currentAttrString ? this.parseValue(currentAttrString) : new OrderedDict(); + const currentAttrString = this.getCurrentValue(element); + const currentAttrsMap = currentAttrString ? this.parseValue(currentAttrString) : new OrderedDict(); // Any outside changes to attributes we add at the end. currentAttrsMap.forEach(function (value, key, i) { @@ -97,7 +98,7 @@ Blaze._DiffingAttributeHandler = AttributeHandler.extend({ attrsMap.append(key, value); }); - var values = []; + const values = []; attrsMap.forEach(function (value, key, i) { values.push(value); }); @@ -106,7 +107,7 @@ Blaze._DiffingAttributeHandler = AttributeHandler.extend({ } }); -var ClassHandler = Blaze._DiffingAttributeHandler.extend({ +const ClassHandler = Blaze._DiffingAttributeHandler.extend({ // @param rawValue {String} getCurrentValue: function (element) { return element.className; @@ -115,7 +116,7 @@ var ClassHandler = Blaze._DiffingAttributeHandler.extend({ element.className = className; }, parseValue: function (attrString) { - var tokens = new OrderedDict(); + const tokens = new OrderedDict(); attrString.split(' ').forEach(function (token) { if (token) { @@ -132,7 +133,7 @@ var ClassHandler = Blaze._DiffingAttributeHandler.extend({ } }); -var SVGClassHandler = ClassHandler.extend({ +const SVGClassHandler = ClassHandler.extend({ getCurrentValue: function (element) { return element.className.baseVal; }, @@ -141,7 +142,7 @@ var SVGClassHandler = ClassHandler.extend({ } }); -var StyleHandler = Blaze._DiffingAttributeHandler.extend({ +const StyleHandler = Blaze._DiffingAttributeHandler.extend({ getCurrentValue: function (element) { return element.getAttribute('style'); }, @@ -158,12 +159,12 @@ var StyleHandler = Blaze._DiffingAttributeHandler.extend({ // Example: // "color:red; foo:12px" produces a token {color: "color:red", foo:"foo:12px"} parseValue: function (attrString) { - var tokens = new OrderedDict(); + const tokens = new OrderedDict(); // Regex for parsing a css attribute declaration, taken from css-parse: // https://github.com/reworkcss/css-parse/blob/7cef3658d0bba872cde05a85339034b187cb3397/index.js#L219 - var regex = /(\*?[-#\/\*\\\w]+(?:\[[0-9a-z_-]+\])?)\s*:\s*(?:\'(?:\\\'|.)*?\'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+[;\s]*/g; - var match = regex.exec(attrString); + const regex = /(\*?[-#\/\*\\\w]+(?:\[[0-9a-z_-]+\])?)\s*:\s*(?:\'(?:\\\'|.)*?\'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+[;\s]*/g; + let match = regex.exec(attrString); while (match) { // match[0] = entire matching string // match[1] = css property @@ -188,9 +189,9 @@ var StyleHandler = Blaze._DiffingAttributeHandler.extend({ } }); -var BooleanHandler = AttributeHandler.extend({ +const BooleanHandler = AttributeHandler.extend({ update: function (element, oldValue, value) { - var name = this.name; + const name = this.name; if (value == null) { if (oldValue != null) element[name] = false; @@ -200,9 +201,9 @@ var BooleanHandler = AttributeHandler.extend({ } }); -var DOMPropertyHandler = AttributeHandler.extend({ +const DOMPropertyHandler = AttributeHandler.extend({ update: function (element, oldValue, value) { - var name = this.name; + const name = this.name; if (value !== element[name]) element[name] = value; } @@ -210,9 +211,9 @@ var DOMPropertyHandler = AttributeHandler.extend({ // attributes of the type 'xlink:something' should be set using // the correct namespace in order to work -var XlinkHandler = AttributeHandler.extend({ +const XlinkHandler = AttributeHandler.extend({ update: function(element, oldValue, value) { - var NS = 'http://www.w3.org/1999/xlink'; + const NS = 'http://www.w3.org/1999/xlink'; if (value === null) { if (oldValue !== null) element.removeAttributeNS(NS, this.name); @@ -223,15 +224,15 @@ var XlinkHandler = AttributeHandler.extend({ }); // cross-browser version of `instanceof SVGElement` -var isSVGElement = function (elem) { +const isSVGElement = function (elem) { return 'ownerSVGElement' in elem; }; -var isUrlAttribute = function (tagName, attrName) { +const isUrlAttribute = function (tagName, attrName) { // Compiled from http://www.w3.org/TR/REC-html40/index/attributes.html // and // http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1 - var urlAttrs = { + const urlAttrs = { FORM: ['action'], BODY: ['background'], BLOCKQUOTE: ['cite'], @@ -260,18 +261,19 @@ var isUrlAttribute = function (tagName, attrName) { return true; } - var urlAttrNames = urlAttrs[tagName] || []; + const urlAttrNames = urlAttrs[tagName] || []; return urlAttrNames.includes(attrName); }; // To get the protocol for a URL, we let the browser normalize it for // us, by setting it as the href for an anchor tag and then reading out // the 'protocol' property. +let anchorForNormalization if (Meteor.isClient) { - var anchorForNormalization = document.createElement('A'); + anchorForNormalization = document.createElement('A'); } -var getUrlProtocol = function (url) { +const getUrlProtocol = function (url) { if (Meteor.isClient) { anchorForNormalization.href = url; return (anchorForNormalization.protocol || "").toLowerCase(); @@ -285,17 +287,17 @@ var getUrlProtocol = function (url) { // Blaze._allowJavascriptUrls() has been called. To detect javascript: // urls, we set the attribute on a dummy anchor element and then read // out the 'protocol' property of the attribute. -var origUpdate = AttributeHandler.prototype.update; -var UrlHandler = AttributeHandler.extend({ +const origUpdate = AttributeHandler.prototype.update; +const UrlHandler = AttributeHandler.extend({ update: function (element, oldValue, value) { - var self = this; - var args = arguments; + const self = this; + const args = arguments; if (Blaze._javascriptUrlsAllowed()) { origUpdate.apply(self, args); } else { - var isJavascriptProtocol = (getUrlProtocol(value) === "javascript:"); - var isVBScriptProtocol = (getUrlProtocol(value) === "vbscript:"); + const isJavascriptProtocol = (getUrlProtocol(value) === "javascript:"); + const isVBScriptProtocol = (getUrlProtocol(value) === "vbscript:"); if (isJavascriptProtocol || isVBScriptProtocol) { Blaze._warn("URLs that use the 'javascript:' or 'vbscript:' protocol are not " + "allowed in URL attribute values. " + @@ -337,9 +339,6 @@ Blaze._makeAttributeHandler = function (elem, name, value) { } else { return new AttributeHandler(name, value); } - - // XXX will need one for 'style' on IE, though modern browsers - // seem to handle setAttribute ok. }; ElementAttributesUpdater = function (elem) { @@ -350,28 +349,28 @@ ElementAttributesUpdater = function (elem) { // Update attributes on `elem` to the dictionary `attrs`, whose // values are strings. ElementAttributesUpdater.prototype.update = function(newAttrs) { - var elem = this.elem; - var handlers = this.handlers; + const elem = this.elem; + const handlers = this.handlers; - for (var k in handlers) { + Object.getOwnPropertyNames(handlers).forEach((k) => { if (!has(newAttrs, k)) { // remove attributes (and handlers) for attribute names // that don't exist as keys of `newAttrs` and so won't // be visited when traversing it. (Attributes that // exist in the `newAttrs` object but are `null` // are handled later.) - var handler = handlers[k]; - var oldValue = handler.value; + const handler = handlers[k]; + const oldValue = handler.value; handler.value = null; handler.update(elem, oldValue, null); delete handlers[k]; } - } + }) - for (var k in newAttrs) { - var handler = null; - var oldValue = null; - var value = newAttrs[k]; + Object.getOwnPropertyNames(newAttrs).forEach((k) => { + let handler = null; + let oldValue = null; + const value = newAttrs[k]; if (!has(handlers, k)) { if (value !== null) { // make new handler @@ -388,5 +387,5 @@ ElementAttributesUpdater.prototype.update = function(newAttrs) { if (value === null) delete handlers[k]; } - } + }) }; diff --git a/packages/blaze/blaze.d.ts b/packages/blaze/blaze.d.ts index b3f72526b..9a2896ca6 100644 --- a/packages/blaze/blaze.d.ts +++ b/packages/blaze/blaze.d.ts @@ -1,25 +1,127 @@ -import { Blaze } from 'meteor/blaze'; - -export namespace Meteor { - - /** Event **/ - interface Event { - type: string; - target: HTMLElement; - currentTarget: HTMLElement; - which: number; - stopPropagation(): void; - stopImmediatePropagation(): void; - preventDefault(): void; - isPropagationStopped(): boolean; - isImmediatePropagationStopped(): boolean; - isDefaultPrevented(): boolean; - } - interface EventHandlerFunction extends Function { - (event?: Event, templateInstance?: Blaze.TemplateInstance): void; - } - interface EventMap { - [id: string]: EventHandlerFunction; +import * as $ from 'jquery'; + +import { Tracker } from 'meteor/tracker'; +import { Meteor } from 'meteor/meteor'; +declare module 'meteor/blaze' { + namespace Blaze { + var View: ViewStatic; + + interface ViewStatic { + new (name?: string, renderFunction?: Function): View; + } + + interface View { + name: string; + parentView: View; + isCreated: boolean; + isRendered: boolean; + isDestroyed: boolean; + renderCount: number; + autorun(runFunc: (computation: Tracker.Computation) => void): Tracker.Computation; + onViewCreated(func: Function): void; + onViewReady(func: Function): void; + onViewDestroyed(func: Function): void; + firstNode(): Node; + lastNode(): Node; + template: Template; + templateInstance(): TemplateInstance; + } + var currentView: View; + + function isTemplate(value: any): boolean; + + interface HelpersMap { + [key: string]: Function; + } + + interface EventsMap> { + [key: string]: (event: Meteor.Event, instance: T) => any; + } + + var Template: TemplateStatic; + + interface TemplateStatic> { + new (viewName?: string, renderFunction?: Function): Template; + + registerHelper(name: string, func: Function): void; + instance(): T; + currentData(): D; + parentData(numLevels?: number): Record; + } + + interface Template> { + viewName: string; + renderFunction: Function; + constructView(): View; + head: Template; + find(selector: string): HTMLElement; + findAll(selector: string): HTMLElement[]; + $: typeof $; + /** + * Register a function to be called when an instance of this template is created. + * @param callback A function to be added as a callback. + */ + onCreated(callback: (this: T) => any): void; + /** + * Register a function to be called when an instance of this template is inserted into the DOM. + * @param callback A function to be added as a callback. + */ + onRendered(callback: (this: T) => any): void; + /** + * Register a function to be called when an instance of this template is removed from the DOM and destroyed. + * @param callback A function to be added as a callback. + */ + onDestroyed(callback: (this: T) => any): void; + created: Function; + rendered: Function; + destroyed: Function; + helpers(helpersMap: HelpersMap): void; + events(eventsMap: EventsMap): void; + } + + class TemplateInstance { + constructor(view: View); + + $(selector: string): JQuery; + autorun(runFunc: (computation: Tracker.Computation) => void): Tracker.Computation; + data: D; + find(selector: string): HTMLElement; + findAll(selector: string): HTMLElement[]; + firstNode: Object; + lastNode: Object; + subscribe(name: string, ...args: any[]): Meteor.SubscriptionHandle; + subscriptionsReady(): boolean; + view: Object; + } + + function Each(argFunc: Function, contentFunc: Function, elseFunc?: Function): View; + + function Unless(conditionFunc: Function, contentFunc: Function, elseFunc?: Function): View; + + function If(conditionFunc: Function, contentFunc: Function, elseFunc?: Function): View; + + function Let(bindings: Function, contentFunc: Function): View; + + function With(data: Object | Function, contentFunc: Function): View; + + function getData(elementOrView?: HTMLElement | View): Object; + + function getView(element?: HTMLElement): View; + + function remove(renderedView: View): void; + + function render(templateOrView: Template | View, parentNode: Node, nextNode?: Node, parentView?: View): View; + + function renderWithData( + templateOrView: Template | View, + data: Object | Function, + parentNode: Node, + nextNode?: Node, + parentView?: View, + ): View; + + function toHTML(templateOrView: Template | View): string; + + function toHTMLWithData(templateOrView: Template | View, data: Object | Function): string; } - /** Event **/ } diff --git a/packages/blaze/builtins.js b/packages/blaze/builtins.js index 044e68a45..3740f7cb6 100644 --- a/packages/blaze/builtins.js +++ b/packages/blaze/builtins.js @@ -2,53 +2,103 @@ import has from 'lodash.has'; import isObject from 'lodash.isobject'; Blaze._calculateCondition = function (cond) { - if (HTML.isArray(cond) && cond.length === 0) - cond = false; - return !! cond; + if (HTML.isArray(cond) && cond.length === 0) return false; + return !!cond; }; /** * @summary Constructs a View that renders content with a data context. * @locus Client - * @param {Object|Function} data An object to use as the data context, or a function returning such an object. If a function is provided, it will be reactively re-run. + * @param {Object|Function} data An object to use as the data context, or a function returning such an object. If a + * function is provided, it will be reactively re-run. * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). */ Blaze.With = function (data, contentFunc) { - var view = Blaze.View('with', contentFunc); + const view = Blaze.View('with', contentFunc); - view.dataVar = new ReactiveVar; - - view.onViewCreated(function () { - if (typeof data === 'function') { - // `data` is a reactive function - view.autorun(function () { - view.dataVar.set(data()); - }, view.parentView, 'setData'); - } else { - view.dataVar.set(data); - } + view.dataVar = null; + view.onViewCreated(() => { + view.dataVar = _createBinding(view, data, 'setData'); }); return view; }; + +/** + * @summary Shallow compare of two bindings. + * @param {Binding} x + * @param {Binding} y + */ +function _isEqualBinding(x, y) { + if (typeof x === 'object' && typeof y === 'object') { + return x.error === y.error && ReactiveVar._isEqual(x.value, y.value); + } + else { + return ReactiveVar._isEqual(x, y); + } +} + +/** + * @template T + * @param {T} x + * @returns {T} + */ +function _identity(x) { + return x; +} + +/** + * Attaches a single binding to the instantiated view. + * @template T, U + * @param {ReactiveVar} reactiveVar Target view. + * @param {Promise | T} value Bound value. + * @param {function(T): U} [mapper] Maps the computed value before store. + */ +function _setBindingValue(reactiveVar, value, mapper = _identity) { + if (value && typeof value.then === 'function') { + value.then( + value => reactiveVar.set({ value: mapper(value) }), + error => reactiveVar.set({ error }), + ); + } else { + reactiveVar.set({ value: mapper(value) }); + } +} + +/** + * @template T, U + * @param {Blaze.View} view Target view. + * @param {Promise | T | function(): (Promise | T)} binding Binding value or its getter. + * @param {string} [displayName] Autorun's display name. + * @param {function(T): U} [mapper] Maps the computed value before store. + * @returns {ReactiveVar} + */ +function _createBinding(view, binding, displayName, mapper) { + const reactiveVar = new ReactiveVar(undefined, _isEqualBinding); + if (typeof binding === 'function') { + view.autorun( + () => _setBindingValue(reactiveVar, binding(), mapper), + view.parentView, + displayName, + ); + } else { + _setBindingValue(reactiveVar, binding, mapper); + } + + return reactiveVar; +} + /** * Attaches bindings to the instantiated view. * @param {Object} bindings A dictionary of bindings, each binding name * corresponds to a value or a function that will be reactively re-run. - * @param {View} view The target. + * @param {Blaze.View} view The target. */ Blaze._attachBindingsToView = function (bindings, view) { view.onViewCreated(function () { Object.entries(bindings).forEach(function ([name, binding]) { - view._scopeBindings[name] = new ReactiveVar(); - if (typeof binding === 'function') { - view.autorun(function () { - view._scopeBindings[name].set(binding()); - }, view.parentView); - } else { - view._scopeBindings[name].set(binding); - } + view._scopeBindings[name] = _createBinding(view, binding); }); }); }; @@ -69,23 +119,33 @@ Blaze.Let = function (bindings, contentFunc) { /** * @summary Constructs a View that renders content conditionally. * @locus Client - * @param {Function} conditionFunc A function to reactively re-run. Whether the result is truthy or falsy determines whether `contentFunc` or `elseFunc` is shown. An empty array is considered falsy. + * @param {Function} conditionFunc A function to reactively re-run. Whether the result is truthy or falsy determines + * whether `contentFunc` or `elseFunc` is shown. An empty array is considered falsy. * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). - * @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#Renderable-Content). If no `elseFunc` is supplied, no content is shown in the "else" case. + * @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#Renderable-Content). If no + * `elseFunc` is supplied, no content is shown in the "else" case. */ Blaze.If = function (conditionFunc, contentFunc, elseFunc, _not) { - var conditionVar = new ReactiveVar; + const view = Blaze.View(_not ? 'unless' : 'if', function () { + // Render only if the binding has a value, i.e., it's either synchronous or + // has resolved. Rejected `Promise`s are NOT rendered. + const condition = view.__conditionVar.get(); + if (condition && 'value' in condition) { + return condition.value ? contentFunc() : (elseFunc ? elseFunc() : null); + } - var view = Blaze.View(_not ? 'unless' : 'if', function () { - return conditionVar.get() ? contentFunc() : - (elseFunc ? elseFunc() : null); + return null; }); - view.__conditionVar = conditionVar; - view.onViewCreated(function () { - this.autorun(function () { - var cond = Blaze._calculateCondition(conditionFunc()); - conditionVar.set(_not ? (! cond) : cond); - }, this.parentView, 'condition'); + + view.__conditionVar = null; + view.onViewCreated(() => { + view.__conditionVar = _createBinding( + view, + conditionFunc, + 'condition', + // Store only the actual condition. + value => !Blaze._calculateCondition(value) !== !_not, + ); }); return view; @@ -94,9 +154,11 @@ Blaze.If = function (conditionFunc, contentFunc, elseFunc, _not) { /** * @summary An inverted [`Blaze.If`](#Blaze-If). * @locus Client - * @param {Function} conditionFunc A function to reactively re-run. If the result is falsy, `contentFunc` is shown, otherwise `elseFunc` is shown. An empty array is considered falsy. + * @param {Function} conditionFunc A function to reactively re-run. If the result is falsy, `contentFunc` is shown, + * otherwise `elseFunc` is shown. An empty array is considered falsy. * @param {Function} contentFunc A Function that returns [*renderable content*](#Renderable-Content). - * @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#Renderable-Content). If no `elseFunc` is supplied, no content is shown in the "else" case. + * @param {Function} [elseFunc] Optional. A Function that returns [*renderable content*](#Renderable-Content). If no + * `elseFunc` is supplied, no content is shown in the "else" case. */ Blaze.Unless = function (conditionFunc, contentFunc, elseFunc) { return Blaze.If(conditionFunc, contentFunc, elseFunc, true /*_not*/); @@ -123,8 +185,8 @@ Blaze.Unless = function (conditionFunc, contentFunc, elseFunc) { * in the sequence. */ Blaze.Each = function (argFunc, contentFunc, elseFunc) { - var eachView = Blaze.View('each', function () { - var subviews = this.initialSubviews; + const eachView = Blaze.View('each', function () { + const subviews = this.initialSubviews; this.initialSubviews = null; if (this._isCreatedForExpansion) { this.expandedValueDep = new Tracker.Dependency; @@ -138,43 +200,44 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { eachView.stopHandle = null; eachView.contentFunc = contentFunc; eachView.elseFunc = elseFunc; - eachView.argVar = new ReactiveVar; + eachView.argVar = undefined; eachView.variableName = null; // update the @index value in the scope of all subviews in the range - var updateIndices = function (from, to) { + const updateIndices = function (from, to) { if (to === undefined) { to = eachView.numItems - 1; } - for (var i = from; i <= to; i++) { - var view = eachView._domrange.members[i].view; - view._scopeBindings['@index'].set(i); + for (let i = from; i <= to; i++) { + const view = eachView._domrange.members[i].view; + view._scopeBindings['@index'].set({ value: i }); } }; eachView.onViewCreated(function () { - // We evaluate argFunc in an autorun to make sure - // Blaze.currentView is always set when it runs (rather than - // passing argFunc straight to ObserveSequence). - eachView.autorun(function () { - // argFunc can return either a sequence as is or a wrapper object with a - // _sequence and _variable fields set. - var arg = argFunc(); - if (isObject(arg) && has(arg, '_sequence')) { - eachView.variableName = arg._variable || null; - arg = arg._sequence; - } - - eachView.argVar.set(arg); - }, eachView.parentView, 'collection'); + // We evaluate `argFunc` in `Tracker.autorun` to ensure `Blaze.currentView` + // is always set when it runs. + eachView.argVar = _createBinding( + eachView, + // Unwrap a sequence reactively (`{{#each x in xs}}`). + () => { + let maybeSequence = argFunc(); + if (isObject(maybeSequence) && has(maybeSequence, '_sequence')) { + eachView.variableName = maybeSequence._variable || null; + maybeSequence = maybeSequence._sequence; + } + return maybeSequence; + }, + 'collection', + ); eachView.stopHandle = ObserveSequence.observe(function () { - return eachView.argVar.get(); + return eachView.argVar.get()?.value; }, { addedAt: function (id, item, index) { Tracker.nonreactive(function () { - var newItemView; + let newItemView; if (eachView.variableName) { // new-style #each (as in {{#each item in items}}) // doesn't create a new data context @@ -185,7 +248,7 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { eachView.numItems++; - var bindings = {}; + const bindings = {}; bindings['@index'] = index; if (eachView.variableName) { bindings[eachView.variableName] = item; @@ -200,7 +263,7 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { eachView.inElseMode = false; } - var range = Blaze._materializeView(newItemView, eachView); + const range = Blaze._materializeView(newItemView, eachView); eachView._domrange.addMember(range, index); updateIndices(index); } else { @@ -233,16 +296,16 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { if (eachView.expandedValueDep) { eachView.expandedValueDep.changed(); } else { - var itemView; + let itemView; if (eachView._domrange) { itemView = eachView._domrange.getMember(index).view; } else { itemView = eachView.initialSubviews[index]; } if (eachView.variableName) { - itemView._scopeBindings[eachView.variableName].set(newItem); + itemView._scopeBindings[eachView.variableName].set({ value: newItem }); } else { - itemView.dataVar.set(newItem); + itemView.dataVar.set({ value: newItem }); } } }); @@ -256,8 +319,8 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { updateIndices( Math.min(fromIndex, toIndex), Math.max(fromIndex, toIndex)); } else { - var subviews = eachView.initialSubviews; - var itemView = subviews[fromIndex]; + const subviews = eachView.initialSubviews; + const itemView = subviews[fromIndex]; subviews.splice(fromIndex, 1); subviews.splice(toIndex, 0, itemView); } @@ -280,10 +343,23 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { return eachView; }; +/** + * Create a new `Blaze.Let` view that unwraps the given value. + * @param {unknown} value + * @returns {Blaze.View} + */ +Blaze._Await = function (value) { + return Blaze.Let({ value }, Blaze._AwaitContent); +}; + +Blaze._AwaitContent = function () { + return Blaze.currentView._scopeBindings.value.get()?.value; +}; + Blaze._TemplateWith = function (arg, contentFunc) { - var w; + let w; - var argFunc = arg; + let argFunc = arg; if (typeof arg !== 'function') { argFunc = function () { return arg; @@ -301,8 +377,8 @@ Blaze._TemplateWith = function (arg, contentFunc) { // // To make this better, reconsider _InOuterTemplateScope as a primitive. // Longer term, evaluate expressions in the proper lexical scope. - var wrappedArgFunc = function () { - var viewToEvaluateArg = null; + const wrappedArgFunc = function () { + let viewToEvaluateArg = null; if (w.parentView && w.parentView.name === 'InOuterTemplateScope') { viewToEvaluateArg = w.parentView.originalParentView; } @@ -313,8 +389,8 @@ Blaze._TemplateWith = function (arg, contentFunc) { } }; - var wrappedContentFunc = function () { - var content = contentFunc.call(this); + const wrappedContentFunc = function () { + let content = contentFunc.call(this); // Since we are generating the Blaze._TemplateWith view for the // user, set the flag on the child view. If `content` is a template, @@ -335,8 +411,8 @@ Blaze._TemplateWith = function (arg, contentFunc) { }; Blaze._InOuterTemplateScope = function (templateView, contentFunc) { - var view = Blaze.View('InOuterTemplateScope', contentFunc); - var parentView = templateView.parentView; + const view = Blaze.View('InOuterTemplateScope', contentFunc); + let parentView = templateView.parentView; // Hack so that if you call `{{> foo bar}}` and it expands into // `{{#with bar}}{{> foo}}{{/with}}`, and then `foo` is a template diff --git a/packages/blaze/dombackend.js b/packages/blaze/dombackend.js index f8be19492..642465664 100644 --- a/packages/blaze/dombackend.js +++ b/packages/blaze/dombackend.js @@ -5,10 +5,10 @@ // it would require that every import be alone in a file with an import to it's predecessor import "meteor/jquery"; -var DOMBackend = {}; +const DOMBackend = {}; Blaze._DOMBackend = DOMBackend; -var $jq = (typeof jQuery !== 'undefined' ? jQuery : +const $jq = (typeof jQuery !== 'undefined' ? jQuery : (typeof Package !== 'undefined' ? Package.jquery && Package.jquery.jQuery : null)); if (! $jq) @@ -16,13 +16,32 @@ if (! $jq) DOMBackend._$jq = $jq; + +DOMBackend.getContext = function() { + if (DOMBackend._context) { + return DOMBackend._context; + } + if ( DOMBackend._$jq.support.createHTMLDocument ) { + DOMBackend._context = document.implementation.createHTMLDocument( "" ); + + // Set the base href for the created document + // so any parsed elements with URLs + // are based on the document's URL (gh-2965) + const base = DOMBackend._context.createElement( "base" ); + base.href = document.location.href; + DOMBackend._context.head.appendChild( base ); + } else { + DOMBackend._context = document; + } + return DOMBackend._context; +} DOMBackend.parseHTML = function (html) { // Return an array of nodes. // // jQuery does fancy stuff like creating an appropriate // container element and setting innerHTML on it, as well // as working around various IE quirks. - return $jq.parseHTML(html) || []; + return $jq.parseHTML(html, DOMBackend.getContext()) || []; }; DOMBackend.Events = { @@ -38,9 +57,9 @@ DOMBackend.Events = { }, bindEventCapturer: function (elem, type, selector, handler) { - var $elem = $jq(elem); + const $elem = $jq(elem); - var wrapper = function (event) { + const wrapper = function (event) { event = $jq.event.fix(event); event.currentTarget = event.target; @@ -50,7 +69,7 @@ DOMBackend.Events = { // since jQuery can't bind capturing handlers, it's not clear // where we would hook in. Internal jQuery functions like `dispatch` // are too high-level. - var $target = $jq(event.currentTarget); + const $target = $jq(event.currentTarget); if ($target.is($elem.find(selector))) handler.call(elem, event); }; @@ -69,7 +88,7 @@ DOMBackend.Events = { parseEventType: function (type) { // strip off namespaces - var dotLoc = type.indexOf('.'); + const dotLoc = type.indexOf('.'); if (dotLoc >= 0) return type.slice(0, dotLoc); return type; @@ -87,10 +106,10 @@ DOMBackend.Events = { // which we can detect using a custom event with a teardown // hook. -var NOOP = function () {}; +const NOOP = function () {}; // Circular doubly-linked list -var TeardownCallback = function (func) { +const TeardownCallback = function (func) { this.next = this; this.prev = this; this.func = func; @@ -110,7 +129,7 @@ TeardownCallback.prototype.unlink = function () { }; TeardownCallback.prototype.go = function () { - var func = this.func; + const func = this.func; func && func(); }; @@ -124,9 +143,9 @@ DOMBackend.Teardown = { // The callback function is called at most once, and it receives the element // in question as an argument. onElementTeardown: function (elem, func) { - var elt = new TeardownCallback(func); + const elt = new TeardownCallback(func); - var propName = DOMBackend.Teardown._CB_PROP; + const propName = DOMBackend.Teardown._CB_PROP; if (! elem[propName]) { // create an empty node that is never unlinked elem[propName] = new TeardownCallback; @@ -142,11 +161,11 @@ DOMBackend.Teardown = { // Recursively call all teardown hooks, in the backend and registered // through DOMBackend.onElementTeardown. tearDownElement: function (elem) { - var elems = []; + const elems = []; // Array.prototype.slice.call doesn't work when given a NodeList in // IE8 ("JScript object expected"). - var nodeList = elem.getElementsByTagName('*'); - for (var i = 0; i < nodeList.length; i++) { + const nodeList = elem.getElementsByTagName('*'); + for (let i = 0; i < nodeList.length; i++) { elems.push(nodeList[i]); } elems.push(elem); @@ -162,10 +181,10 @@ $jq.event.special[DOMBackend.Teardown._JQUERY_EVENT_NAME] = { // feature enabled. }, teardown: function() { - var elem = this; - var callbacks = elem[DOMBackend.Teardown._CB_PROP]; + const elem = this; + const callbacks = elem[DOMBackend.Teardown._CB_PROP]; if (callbacks) { - var elt = callbacks.next; + let elt = callbacks.next; while (elt !== callbacks) { elt.go(); elt = elt.next; diff --git a/packages/blaze/domrange.js b/packages/blaze/domrange.js index f9baa465f..0ff8854fd 100644 --- a/packages/blaze/domrange.js +++ b/packages/blaze/domrange.js @@ -1,6 +1,6 @@ // A constant empty array (frozen if the JS engine supports it). -var _emptyArray = Object.freeze ? Object.freeze([]) : []; +const _emptyArray = Object.freeze ? Object.freeze([]) : []; // `[new] Blaze._DOMRange([nodeAndRangeArray])` // @@ -13,11 +13,11 @@ Blaze._DOMRange = function (nodeAndRangeArray) { // called without `new` return new DOMRange(nodeAndRangeArray); - var members = (nodeAndRangeArray || _emptyArray); + const members = (nodeAndRangeArray || _emptyArray); if (! (members && (typeof members.length) === 'number')) throw new Error("Expected array"); - for (var i = 0; i < members.length; i++) + for (let i = 0; i < members.length; i++) this._memberIn(members[i]); this.members = members; @@ -27,7 +27,7 @@ Blaze._DOMRange = function (nodeAndRangeArray) { this.parentRange = null; this.attachedCallbacks = _emptyArray; }; -var DOMRange = Blaze._DOMRange; +const DOMRange = Blaze._DOMRange; // In IE 8, don't use empty text nodes as placeholders // in empty DOMRanges, use comment nodes instead. Using @@ -40,8 +40,8 @@ var DOMRange = Blaze._DOMRange; // even though we don't need to set properties on the // placeholder anymore. DOMRange._USE_COMMENT_PLACEHOLDERS = (function () { - var result = false; - var textNode = document.createTextNode(""); + let result = false; + const textNode = document.createTextNode(""); try { textNode.someProp = true; } catch (e) { @@ -53,7 +53,7 @@ DOMRange._USE_COMMENT_PLACEHOLDERS = (function () { // static methods DOMRange._insert = function (rangeOrNode, parentElement, nextNode, _isMove) { - var m = rangeOrNode; + const m = rangeOrNode; if (m instanceof DOMRange) { m.attach(parentElement, nextNode, _isMove); } else { @@ -65,7 +65,7 @@ DOMRange._insert = function (rangeOrNode, parentElement, nextNode, _isMove) { }; DOMRange._remove = function (rangeOrNode) { - var m = rangeOrNode; + const m = rangeOrNode; if (m instanceof DOMRange) { m.detach(); } else { @@ -111,7 +111,7 @@ DOMRange._moveNodeWithHooks = function (n, parent, next) { DOMRange.forElement = function (elem) { if (elem.nodeType !== 1) throw new Error("Expected element, found: " + elem); - var range = null; + let range = null; while (elem && ! range) { range = (elem.$blaze_range || null); if (! range) @@ -133,14 +133,14 @@ DOMRange.prototype.attach = function (parentElement, nextNode, _isMove, _isRepla throw new Error("Can only move or replace an attached DOMRange, and only under the same parent element"); } - var members = this.members; + const members = this.members; if (members.length) { this.emptyRangePlaceholder = null; - for (var i = 0; i < members.length; i++) { + for (let i = 0; i < members.length; i++) { DOMRange._insert(members[i], parentElement, nextNode, _isMove); } } else { - var placeholder = ( + const placeholder = ( DOMRange._USE_COMMENT_PLACEHOLDERS ? document.createComment("") : document.createTextNode("")); @@ -151,23 +151,23 @@ DOMRange.prototype.attach = function (parentElement, nextNode, _isMove, _isRepla this.parentElement = parentElement; if (! (_isMove || _isReplace)) { - for(var i = 0; i < this.attachedCallbacks.length; i++) { - var obj = this.attachedCallbacks[i]; + for(let i = 0; i < this.attachedCallbacks.length; i++) { + const obj = this.attachedCallbacks[i]; obj.attached && obj.attached(this, parentElement); } } }; DOMRange.prototype.setMembers = function (newNodeAndRangeArray) { - var newMembers = newNodeAndRangeArray; + const newMembers = newNodeAndRangeArray; if (! (newMembers && (typeof newMembers.length) === 'number')) throw new Error("Expected array"); - var oldMembers = this.members; + const oldMembers = this.members; - for (var i = 0; i < oldMembers.length; i++) + for (let i = 0; i < oldMembers.length; i++) this._memberOut(oldMembers[i]); - for (var i = 0; i < newMembers.length; i++) + for (let i = 0; i < newMembers.length; i++) this._memberIn(newMembers[i]); if (! this.attached) { @@ -176,8 +176,8 @@ DOMRange.prototype.setMembers = function (newNodeAndRangeArray) { // don't do anything if we're going from empty to empty if (newMembers.length || oldMembers.length) { // detach the old members and insert the new members - var nextNode = this.lastNode().nextSibling; - var parentElement = this.parentElement; + const nextNode = this.lastNode().nextSibling; + const parentElement = this.parentElement; // Use detach/attach, but don't fire attached/detached hooks this.detach(true /*_isReplace*/); this.members = newMembers; @@ -193,7 +193,7 @@ DOMRange.prototype.firstNode = function () { if (! this.members.length) return this.emptyRangePlaceholder; - var m = this.members[0]; + const m = this.members[0]; return (m instanceof DOMRange) ? m.firstNode() : m; }; @@ -204,7 +204,7 @@ DOMRange.prototype.lastNode = function () { if (! this.members.length) return this.emptyRangePlaceholder; - var m = this.members[this.members.length - 1]; + const m = this.members[this.members.length - 1]; return (m instanceof DOMRange) ? m.lastNode() : m; }; @@ -212,14 +212,14 @@ DOMRange.prototype.detach = function (_isReplace) { if (! this.attached) throw new Error("Must be attached"); - var oldParentElement = this.parentElement; - var members = this.members; + const oldParentElement = this.parentElement; + const members = this.members; if (members.length) { - for (var i = 0; i < members.length; i++) { + for (let i = 0; i < members.length; i++) { DOMRange._remove(members[i]); } } else { - var placeholder = this.emptyRangePlaceholder; + const placeholder = this.emptyRangePlaceholder; this.parentElement.removeChild(placeholder); this.emptyRangePlaceholder = null; } @@ -228,15 +228,15 @@ DOMRange.prototype.detach = function (_isReplace) { this.attached = false; this.parentElement = null; - for(var i = 0; i < this.attachedCallbacks.length; i++) { - var obj = this.attachedCallbacks[i]; + for(let i = 0; i < this.attachedCallbacks.length; i++) { + const obj = this.attachedCallbacks[i]; obj.detached && obj.detached(this, oldParentElement); } } }; DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) { - var members = this.members; + const members = this.members; if (! (atIndex >= 0 && atIndex <= members.length)) throw new Error("Bad index in range.addMember: " + atIndex); @@ -250,12 +250,12 @@ DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) { // empty; use the empty-to-nonempty handling of setMembers this.setMembers([newMember]); } else { - var nextNode; + let nextNode; if (atIndex === members.length) { // insert at end nextNode = this.lastNode().nextSibling; } else { - var m = members[atIndex]; + const m = members[atIndex]; nextNode = (m instanceof DOMRange) ? m.firstNode() : m; } members.splice(atIndex, 0, newMember); @@ -264,14 +264,14 @@ DOMRange.prototype.addMember = function (newMember, atIndex, _isMove) { }; DOMRange.prototype.removeMember = function (atIndex, _isMove) { - var members = this.members; + const members = this.members; if (! (atIndex >= 0 && atIndex < members.length)) throw new Error("Bad index in range.removeMember: " + atIndex); if (_isMove) { members.splice(atIndex, 1); } else { - var oldMember = members[atIndex]; + const oldMember = members[atIndex]; this._memberOut(oldMember); if (members.length === 1) { @@ -286,13 +286,13 @@ DOMRange.prototype.removeMember = function (atIndex, _isMove) { }; DOMRange.prototype.moveMember = function (oldIndex, newIndex) { - var member = this.members[oldIndex]; + const member = this.members[oldIndex]; this.removeMember(oldIndex, true /*_isMove*/); this.addMember(member, newIndex, true /*_isMove*/); }; DOMRange.prototype.getMember = function (atIndex) { - var members = this.members; + const members = this.members; if (! (atIndex >= 0 && atIndex < members.length)) throw new Error("Bad index in range.getMember: " + atIndex); return this.members[atIndex]; @@ -323,8 +323,8 @@ DOMRange.prototype._memberOut = DOMRange._destroy; // Tear down, but don't remove, the members. Used when chunks // of DOM are being torn down or replaced. DOMRange.prototype.destroyMembers = function (_skipNodes) { - var members = this.members; - for (var i = 0; i < members.length; i++) + const members = this.members; + for (let i = 0; i < members.length; i++) this._memberOut(members[i], _skipNodes); }; @@ -337,7 +337,7 @@ DOMRange.prototype.containsElement = function (elem, selector, event) { ? this.view.name.split('.')[1] : 'unknown template'; if (! this.attached) - throw new Error(`${event} event triggerd with ${selector} on ${templateName} but associated view is not be found. + throw new Error(`${event} event triggered with ${selector} on ${templateName} but associated view is not be found. Make sure the event doesn't destroy the view.`); // An element is contained in this DOMRange if it's possible to @@ -357,7 +357,7 @@ DOMRange.prototype.containsElement = function (elem, selector, event) { while (elem.parentNode !== this.parentElement) elem = elem.parentNode; - var range = elem.$blaze_range; + let range = elem.$blaze_range; while (range && range !== this) range = range.parentRange; @@ -405,9 +405,9 @@ DOMRange.prototype.onAttachedDetached = function (callbacks) { }; DOMRange.prototype.$ = function (selector) { - var self = this; + const self = this; - var parentNode = this.parentElement; + const parentNode = this.parentElement; if (! parentNode) throw new Error("Can't select in removed DomRange"); @@ -424,7 +424,7 @@ DOMRange.prototype.$ = function (selector) { if (parentNode.nodeType === 11 /* DocumentFragment */) throw new Error("Can't use $ on an offscreen range"); - var results = Blaze._DOMBackend.findBySelector(selector, parentNode); + let results = Blaze._DOMBackend.findBySelector(selector, parentNode); // We don't assume `results` has jQuery API; a plain array // should do just as well. However, if we do have a jQuery @@ -434,7 +434,7 @@ DOMRange.prototype.$ = function (selector) { // Function that selects only elements that are actually // in this DomRange, rather than simply descending from // `parentNode`. - var filterFunc = function (elem) { + const filterFunc = function (elem) { // handle jQuery's arguments to filter, where the node // is in `this` and the index is the first argument. if (typeof elem === 'number') @@ -446,9 +446,9 @@ DOMRange.prototype.$ = function (selector) { if (! results.filter) { // not a jQuery array, and not a browser with // Array.prototype.filter (e.g. IE <9) - var newResults = []; - for (var i = 0; i < results.length; i++) { - var x = results[i]; + const newResults = []; + for (let i = 0; i < results.length; i++) { + const x = results[i]; if (filterFunc(x)) newResults.push(x); } diff --git a/packages/blaze/events.js b/packages/blaze/events.js index 4dfaa4dc0..e1051d4c4 100644 --- a/packages/blaze/events.js +++ b/packages/blaze/events.js @@ -1,8 +1,8 @@ import has from 'lodash.has'; -var EventSupport = Blaze._EventSupport = {}; +const EventSupport = Blaze._EventSupport = {}; -var DOMBackend = Blaze._DOMBackend; +const DOMBackend = Blaze._DOMBackend; // List of events to always delegate, never capture. // Since jQuery fakes bubbling for certain events in @@ -12,20 +12,26 @@ var DOMBackend = Blaze._DOMBackend; // We could list all known bubbling // events here to avoid creating speculative capturers // for them, but it would only be an optimization. -var eventsToDelegate = EventSupport.eventsToDelegate = { - blur: 1, change: 1, click: 1, focus: 1, focusin: 1, - focusout: 1, reset: 1, submit: 1 +const eventsToDelegate = EventSupport.eventsToDelegate = { + blur: 1, + change: 1, + click: 1, + focus: 1, + focusin: 1, + focusout: 1, + reset: 1, + submit: 1 }; -var EVENT_MODE = EventSupport.EVENT_MODE = { +const EVENT_MODE = EventSupport.EVENT_MODE = { TBD: 0, BUBBLING: 1, CAPTURING: 2 }; -var NEXT_HANDLERREC_ID = 1; +let NEXT_HANDLERREC_ID = 1; -var HandlerRec = function (elem, type, selector, handler, recipient) { +const HandlerRec = function (elem, type, selector, handler, recipient) { this.elem = elem; this.type = type; this.selector = selector; @@ -58,7 +64,7 @@ var HandlerRec = function (elem, type, selector, handler, recipient) { // events using capture in all browsers except IE 8. // IE 8 doesn't support these events anyway. - var tryCapturing = elem.addEventListener && + const tryCapturing = elem.addEventListener && (!has(eventsToDelegate, DOMBackend.Events.parseEventType(type))); @@ -129,26 +135,26 @@ EventSupport.listen = function (element, events, selector, handler, recipient, g // Repro: https://github.com/dgreensp/public/tree/master/safari-crash try { element = element; } finally {} - var eventTypes = []; + const eventTypes = []; events.replace(/[^ /]+/g, function (e) { eventTypes.push(e); }); - var newHandlerRecs = []; - for (var i = 0, N = eventTypes.length; i < N; i++) { - var type = eventTypes[i]; + const newHandlerRecs = []; + for (let i = 0, N = eventTypes.length; i < N; i++) { + const type = eventTypes[i]; - var eventDict = element.$blaze_events; + let eventDict = element.$blaze_events; if (! eventDict) eventDict = (element.$blaze_events = {}); - var info = eventDict[type]; + let info = eventDict[type]; if (! info) { info = eventDict[type] = {}; info.handlers = []; } - var handlerList = info.handlers; - var handlerRec = new HandlerRec( + const handlerList = info.handlers; + const handlerRec = new HandlerRec( element, type, selector, handler, recipient); newHandlerRecs.push(handlerRec); handlerRec.bind(); @@ -157,12 +163,12 @@ EventSupport.listen = function (element, events, selector, handler, recipient, g // them. In jQuery (or other DOMBackend) this causes them to fire // later when the backend dispatches event handlers. if (getParentRecipient) { - for (var r = getParentRecipient(recipient); r; + for (let r = getParentRecipient(recipient); r; r = getParentRecipient(r)) { // r is an enclosing range (recipient) - for (var j = 0, Nj = handlerList.length; + for (let j = 0, Nj = handlerList.length; j < Nj; j++) { - var h = handlerList[j]; + const h = handlerList[j]; if (h.recipient === r) { h.unbind(); h.bind(); @@ -179,7 +185,7 @@ EventSupport.listen = function (element, events, selector, handler, recipient, g return { // closes over just `element` and `newHandlerRecs` stop: function () { - var eventDict = element.$blaze_events; + const eventDict = element.$blaze_events; if (! eventDict) return; // newHandlerRecs has only one item unless you specify multiple @@ -187,13 +193,13 @@ EventSupport.listen = function (element, events, selector, handler, recipient, g // iterate over handlerList here. Clearing a whole handlerList // via stop() methods is O(N^2) in the number of handlers on // an element. - for (var i = 0; i < newHandlerRecs.length; i++) { - var handlerToRemove = newHandlerRecs[i]; - var info = eventDict[handlerToRemove.type]; + for (let i = 0; i < newHandlerRecs.length; i++) { + const handlerToRemove = newHandlerRecs[i]; + const info = eventDict[handlerToRemove.type]; if (! info) continue; - var handlerList = info.handlers; - for (var j = handlerList.length - 1; j >= 0; j--) { + const handlerList = info.handlers; + for (let j = handlerList.length - 1; j >= 0; j--) { if (handlerList[j] === handlerToRemove) { handlerToRemove.unbind(); handlerList.splice(j, 1); // remove handlerList[j] diff --git a/packages/blaze/exceptions.js b/packages/blaze/exceptions.js index 429ac82f7..b99ce4c95 100644 --- a/packages/blaze/exceptions.js +++ b/packages/blaze/exceptions.js @@ -1,4 +1,4 @@ -var debugFunc; +let debugFunc; // We call into user code in many places, and it's nice to catch exceptions // propagated from user code immediately so that the whole system doesn't just @@ -42,11 +42,18 @@ Blaze._reportException = function (e, msg) { debugFunc()(msg || 'Exception caught in template:', e.stack || e.message || e); }; +// It's meant to be used in `Promise` chains to report the error while not +// "swallowing" it (i.e., the chain will still reject). +Blaze._reportExceptionAndThrow = function (error) { + Blaze._reportException(error); + throw error; +}; + Blaze._wrapCatchingExceptions = function (f, where) { if (typeof f !== 'function') return f; - return function () { + return function (...arguments) { try { return f.apply(this, arguments); } catch (e) { diff --git a/packages/blaze/lookup.js b/packages/blaze/lookup.js index 87d427238..682e8b98f 100644 --- a/packages/blaze/lookup.js +++ b/packages/blaze/lookup.js @@ -1,6 +1,37 @@ import has from 'lodash.has'; -Blaze._globalHelpers = {}; +/** @param {function(Binding): boolean} fn */ +function _createBindingsHelper(fn) { + /** @param {string[]} names */ + return (...names) => { + const view = Blaze.currentView; + + // There's either zero arguments (i.e., check all bindings) or an additional + // "hash" argument that we have to ignore. + names = names.length === 0 + // TODO: Should we walk up the bindings here? + ? Object.keys(view._scopeBindings) + : names.slice(0, -1); + + return names.some(name => { + const binding = _lexicalBindingLookup(view, name); + if (!binding) { + throw new Error(`Binding for "${name}" was not found.`); + } + + return fn(binding.get()); + }); + }; +} + +Blaze._globalHelpers = { + /** @summary Check whether any of the given bindings (or all if none given) is still pending. */ + '@pending': _createBindingsHelper(binding => binding === undefined), + /** @summary Check whether any of the given bindings (or all if none given) has rejected. */ + '@rejected': _createBindingsHelper(binding => !!binding && 'error' in binding), + /** @summary Check whether any of the given bindings (or all if none given) has resolved. */ + '@resolved': _createBindingsHelper(binding => !!binding && 'value' in binding), +}; // Documented as Template.registerHelper. // This definition also provides back-compat for `UI.registerHelper`. @@ -13,7 +44,7 @@ Blaze.deregisterHelper = function(name) { delete Blaze._globalHelpers[name]; }; -var bindIfIsFunction = function (x, target) { +const bindIfIsFunction = function (x, target) { if (typeof x !== 'function') return x; return Blaze._bind(x, target); @@ -21,13 +52,13 @@ var bindIfIsFunction = function (x, target) { // If `x` is a function, binds the value of `this` for that function // to the current data context. -var bindDataContext = function (x) { +const bindDataContext = function (x) { if (typeof x === 'function') { - return function () { - var data = Blaze.getData(); + return function (...args) { + let data = Blaze.getData(); if (data == null) data = {}; - return x.apply(data, arguments); + return x.apply(data, args); }; } return x; @@ -37,14 +68,15 @@ Blaze._OLDSTYLE_HELPER = {}; Blaze._getTemplateHelper = function (template, name, tmplInstanceFunc) { // XXX COMPAT WITH 0.9.3 - var isKnownOldStyleHelper = false; + let isKnownOldStyleHelper = false; if (template.__helpers.has(name)) { - var helper = template.__helpers.get(name); + const helper = template.__helpers.get(name); if (helper === Blaze._OLDSTYLE_HELPER) { isKnownOldStyleHelper = true; } else if (helper != null) { - return wrapHelper(bindDataContext(helper), tmplInstanceFunc); + const printName = `${template.viewName} ${name}`; + return wrapHelper(bindDataContext(helper), tmplInstanceFunc, printName); } else { return null; } @@ -69,17 +101,16 @@ Blaze._getTemplateHelper = function (template, name, tmplInstanceFunc) { return null; }; -var wrapHelper = function (f, templateFunc) { +const wrapHelper = function (f, templateFunc, name = 'template helper') { if (typeof f !== "function") { return f; } - return function () { - var self = this; - var args = arguments; + return function (...args) { + const self = this; return Blaze.Template._withTemplateInstanceFunc(templateFunc, function () { - return Blaze._wrapCatchingExceptions(f, 'template helper').apply(self, args); + return Blaze._wrapCatchingExceptions(f, name).apply(self, args); }); }; }; @@ -94,7 +125,7 @@ function _lexicalKeepGoing(currentView) { if (currentView.parentView.__childDoesntStartNewLexicalScope) { return currentView.parentView; } - + // in the case of {{> Template.contentBlock data}} the contentBlock loses the lexical scope of it's parent, wheras {{> Template.contentBlock}} it does not // this is because a #with sits between the include InOuterTemplateScope if (currentView.parentView.name === "with" && currentView.parentView.parentView && currentView.parentView.parentView.__childDoesntStartNewLexicalScope) { @@ -103,9 +134,8 @@ function _lexicalKeepGoing(currentView) { return undefined; } -Blaze._lexicalBindingLookup = function (view, name) { - var currentView = view; - var blockHelpersStack = []; +function _lexicalBindingLookup(view, name) { + let currentView = view; // walk up the views stopping at a Spacebars.include or Template view that // doesn't have an InOuterTemplateScope view as a parent @@ -113,14 +143,16 @@ Blaze._lexicalBindingLookup = function (view, name) { // skip block helpers views // if we found the binding on the scope, return it if (has(currentView._scopeBindings, name)) { - var bindingReactiveVar = currentView._scopeBindings[name]; - return function () { - return bindingReactiveVar.get(); - }; + return currentView._scopeBindings[name]; } } while (currentView = _lexicalKeepGoing(currentView)); return null; +} + +Blaze._lexicalBindingLookup = function (view, name) { + const binding = _lexicalBindingLookup(view, name); + return binding && (() => binding.get()?.value); }; // templateInstance argument is provided to be available for possible @@ -134,7 +166,8 @@ Blaze._getTemplate = function (name, templateInstance) { Blaze._getGlobalHelper = function (name, templateInstance) { if (Blaze._globalHelpers[name] != null) { - return wrapHelper(bindDataContext(Blaze._globalHelpers[name]), templateInstance); + const printName = `global helper ${name}`; + return wrapHelper(bindDataContext(Blaze._globalHelpers[name]), templateInstance, printName); } return null; }; @@ -154,12 +187,12 @@ Blaze._getGlobalHelper = function (name, templateInstance) { // dependencies itself. If there is any reactivity in the // value, lookup should return a function. Blaze.View.prototype.lookup = function (name, _options) { - var template = this.template; - var lookupTemplate = _options && _options.template; - var helper; - var binding; - var boundTmplInstance; - var foundTemplate; + const template = this.template; + const lookupTemplate = _options && _options.template; + let helper; + let binding; + let boundTmplInstance; + let foundTemplate; if (this.templateInstance) { boundTmplInstance = Blaze._bind(this.templateInstance, this); @@ -193,15 +226,16 @@ Blaze.View.prototype.lookup = function (name, _options) { } // 4. look up a global helper - if ((helper = Blaze._getGlobalHelper(name, boundTmplInstance)) != null) { + helper = Blaze._getGlobalHelper(name, boundTmplInstance); + if (helper != null) { return helper; } // 5. look up in a data context - return function () { - var isCalledAsFunction = (arguments.length > 0); - var data = Blaze.getData(); - var x = data && data[name]; + return function (...args) { + const isCalledAsFunction = (args.length > 0); + const data = Blaze.getData(); + const x = data && data[name]; if (! x) { if (lookupTemplate) { throw new Error("No such template: " + name); @@ -227,7 +261,7 @@ Blaze.View.prototype.lookup = function (name, _options) { } return x; } - return x.apply(data, arguments); + return x.apply(data, args); }; }; @@ -238,16 +272,16 @@ Blaze._parentData = function (height, _functionWrapped) { if (height == null) { height = 1; } - var theWith = Blaze.getView('with'); - for (var i = 0; (i < height) && theWith; i++) { + let theWith = Blaze.getView('with'); + for (let i = 0; (i < height) && theWith; i++) { theWith = Blaze.getView(theWith, 'with'); } if (! theWith) return null; if (_functionWrapped) - return function () { return theWith.dataVar.get(); }; - return theWith.dataVar.get(); + return function () { return theWith.dataVar.get()?.value; }; + return theWith.dataVar.get()?.value; }; diff --git a/packages/blaze/materializer.js b/packages/blaze/materializer.js index dcd84b143..d81547f13 100644 --- a/packages/blaze/materializer.js +++ b/packages/blaze/materializer.js @@ -21,7 +21,7 @@ Blaze._materializeDOM = function (htmljs, intoArray, parentView, // and run, last first, after materializeDOMInner returns. The // reason we use a stack instead of a queue is so that we recurse // depth-first, doing newer tasks first. - var workStack = (_existingWorkStack || []); + const workStack = (_existingWorkStack || []); materializeDOMInner(htmljs, intoArray, parentView, workStack); if (! _existingWorkStack) { @@ -30,7 +30,7 @@ Blaze._materializeDOM = function (htmljs, intoArray, parentView, // of the stack. while (workStack.length) { // Note that running task() may push new items onto workStack. - var task = workStack.pop(); + const task = workStack.pop(); task(); } } @@ -38,7 +38,7 @@ Blaze._materializeDOM = function (htmljs, intoArray, parentView, return intoArray; }; -var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { +const materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { if (htmljs == null) { // null or undefined return; @@ -63,22 +63,26 @@ var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { case HTML.Raw.htmljsType: // Get an array of DOM nodes by using the browser's HTML parser // (like innerHTML). - var nodes = Blaze._DOMBackend.parseHTML(htmljs.value); - for (var i = 0; i < nodes.length; i++) + const nodes = Blaze._DOMBackend.parseHTML(htmljs.value); + for (let i = 0; i < nodes.length; i++) intoArray.push(nodes[i]); return; } } else if (HTML.isArray(htmljs)) { - for (var i = htmljs.length-1; i >= 0; i--) { + for (let i = htmljs.length-1; i >= 0; i--) { workStack.push(Blaze._bind(Blaze._materializeDOM, null, htmljs[i], intoArray, parentView, workStack)); } return; } else { - if (htmljs instanceof Blaze.Template) { + // Try to construct a `Blaze.View` out of the object. If it works... + if (isPromiseLike(htmljs)) { + htmljs = Blaze._Await(htmljs); + } else if (htmljs instanceof Blaze.Template) { htmljs = htmljs.constructView(); - // fall through to Blaze.View case below } + + // ...materialize it. if (htmljs instanceof Blaze.View) { Blaze._materializeView(htmljs, parentView, workStack, intoArray); return; @@ -89,9 +93,60 @@ var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { throw new Error("Unexpected object in htmljs: " + htmljs); }; -var materializeTag = function (tag, parentView, workStack) { - var tagName = tag.tagName; - var elem; +const isPromiseLike = x => !!x && typeof x.then === 'function'; + +function then(maybePromise, fn) { + if (isPromiseLike(maybePromise)) { + maybePromise.then(fn, Blaze._reportException); + } else { + fn(maybePromise); + } +} + +function waitForAllAttributes(attrs) { + // Non-object attrs (e.g., `null`) are ignored. + if (!attrs || attrs !== Object(attrs)) { + return {}; + } + + // Combined attributes, e.g., ``. + if (Array.isArray(attrs)) { + const mapped = attrs.map(waitForAllAttributes); + return mapped.some(isPromiseLike) ? Promise.all(mapped) : mapped; + } + + // Singular async attributes, e.g., ``. + if (isPromiseLike(attrs)) { + return attrs.then(waitForAllAttributes, Blaze._reportExceptionAndThrow); + } + + // Singular sync attributes, with potentially async properties. + const promises = []; + for (const [key, value] of Object.entries(attrs)) { + if (isPromiseLike(value)) { + promises.push(value.then(value => { + attrs[key] = value; + }, Blaze._reportExceptionAndThrow)); + } else if (Array.isArray(value)) { + value.forEach((element, index) => { + if (isPromiseLike(element)) { + promises.push(element.then(element => { + value[index] = element; + }, Blaze._reportExceptionAndThrow)); + } + }); + } + } + + // If any of the properties were async, lift the `Promise`. + return promises.length + ? Promise.all(promises).then(() => attrs, Blaze._reportExceptionAndThrow) + : attrs; +} + +const materializeTag = function (tag, parentView, workStack) { + const tagName = tag.tagName; + let elem; if ((HTML.isKnownSVGElement(tagName) || isSVGAnchor(tag)) && document.createElementNS) { // inline SVG @@ -101,8 +156,8 @@ var materializeTag = function (tag, parentView, workStack) { elem = document.createElement(tagName); } - var rawAttrs = tag.attrs; - var children = tag.children; + let rawAttrs = tag.attrs; + let children = tag.children; if (tagName === 'textarea' && tag.children.length && ! (rawAttrs && ('value' in rawAttrs))) { // Provide very limited support for TEXTAREA tags with children @@ -122,25 +177,27 @@ var materializeTag = function (tag, parentView, workStack) { } if (rawAttrs) { - var attrUpdater = new ElementAttributesUpdater(elem); - var updateAttributes = function () { - var expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView); - var flattenedAttrs = HTML.flattenAttributes(expandedAttrs); - var stringAttrs = {}; - for (var attrName in flattenedAttrs) { - // map `null`, `undefined`, and `false` to null, which is important - // so that attributes with nully values are considered absent. - // stringify anything else (e.g. strings, booleans, numbers including 0). - if (flattenedAttrs[attrName] == null || flattenedAttrs[attrName] === false) - stringAttrs[attrName] = null; - else - stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName], - parentView, - HTML.TEXTMODE.STRING); - } - attrUpdater.update(stringAttrs); + const attrUpdater = new ElementAttributesUpdater(elem); + const updateAttributes = function () { + const expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView); + then(waitForAllAttributes(expandedAttrs), awaitedAttrs => { + const flattenedAttrs = HTML.flattenAttributes(awaitedAttrs); + const stringAttrs = {}; + Object.keys(flattenedAttrs).forEach((attrName) => { + // map `null`, `undefined`, and `false` to null, which is important + // so that attributes with nully values are considered absent. + // stringify anything else (e.g. strings, booleans, numbers including 0). + if (flattenedAttrs[attrName] == null || flattenedAttrs[attrName] === false) + stringAttrs[attrName] = null; + else + stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName], + parentView, + HTML.TEXTMODE.STRING); + }); + attrUpdater.update(stringAttrs); + }); }; - var updaterComputation; + let updaterComputation; if (parentView) { updaterComputation = parentView.autorun(updateAttributes, undefined, 'updater'); @@ -157,11 +214,11 @@ var materializeTag = function (tag, parentView, workStack) { } if (children.length) { - var childNodesAndRanges = []; + const childNodesAndRanges = []; // push this function first so that it's done last workStack.push(function () { - for (var i = 0; i < childNodesAndRanges.length; i++) { - var x = childNodesAndRanges[i]; + for (let i = 0; i < childNodesAndRanges.length; i++) { + const x = childNodesAndRanges[i]; if (x instanceof Blaze._DOMRange) x.attach(elem); else @@ -178,7 +235,7 @@ var materializeTag = function (tag, parentView, workStack) { }; -var isSVGAnchor = function (node) { +const isSVGAnchor = function (node) { // We generally aren't able to detect SVG elements because // if "A" were in our list of known svg element names, then all // nodes would be created using diff --git a/packages/blaze/package.js b/packages/blaze/package.js index 17f6c9223..209c05833 100644 --- a/packages/blaze/package.js +++ b/packages/blaze/package.js @@ -1,7 +1,7 @@ Package.describe({ name: 'blaze', summary: "Meteor Reactive Templating library", - version: '2.6.1', + version: '3.0.2', git: 'https://github.com/meteor/blaze.git' }); @@ -14,12 +14,12 @@ Npm.depends({ Package.onUse(function (api) { api.use('jquery@1.11.9 || 3.0.0', { weak: true }); // should be a weak dep, by having multiple "DOM backends" - api.use('tracker@1.2.0'); - api.use('check@1.3.1'); - api.use('observe-sequence@1.0.16'); - api.use('reactive-var@1.0.11'); - api.use('ordered-dict@1.1.0'); - api.use('ecmascript@0.15.1'); + api.use('tracker@1.3.2'); + api.use('check@1.0.12'); + api.use('observe-sequence@2.0.0'); + api.use('reactive-var@1.0.12'); + api.use('ordered-dict@1.2.0'); + api.use('ecmascript@0.16.9'); api.export([ 'Blaze', @@ -27,8 +27,8 @@ Package.onUse(function (api) { 'Handlebars' ]); - api.use('htmljs@1.1.1'); - api.imply('htmljs@1.1.1'); + api.use('htmljs@2.0.1'); + api.imply('htmljs@2.0.1'); api.addFiles([ 'preamble.js' @@ -52,19 +52,22 @@ Package.onUse(function (api) { 'template.js', 'backcompat.js' ]); + // Maybe in order to work properly user will need to have Jquery typedefs + api.addAssets('blaze.d.ts', 'server'); }); Package.onTest(function (api) { - api.use('ecmascript@0.15.1'); - api.use('tinytest@1.1.0'); - api.use('test-helpers@1.2.0'); + api.use('ecmascript@0.16.9'); + api.use('tinytest'); + api.use('test-helpers'); api.use('jquery@1.11.9 || 3.0.0'); // strong dependency, for testing jQuery backend - api.use('reactive-var@1.0.11'); - api.use('tracker@1.1.0'); + + api.use('reactive-var@1.0.12'); + api.use('tracker@1.3.2'); api.use('blaze'); - api.use('blaze-tools@1.1.3'); // for BlazeTools.toJS - api.use('html-tools@1.1.3'); + api.use('blaze-tools@2.0.0'); // for BlazeTools.toJS + api.use('html-tools@2.0.0'); api.use('templating'); api.addFiles('view_tests.js'); diff --git a/packages/blaze/preamble.js b/packages/blaze/preamble.js index 8d1291b8e..33b2a6083 100644 --- a/packages/blaze/preamble.js +++ b/packages/blaze/preamble.js @@ -8,7 +8,7 @@ Blaze = {}; // TODO: Should be replaced with _.escape once underscore is upgraded to a newer // version which escapes ` (backtick) as well. Underscore 1.5.2 does not. Blaze._escape = (function() { - var escape_map = { + const escape_map = { "<": "<", ">": ">", '"': """, @@ -17,7 +17,7 @@ Blaze._escape = (function() { "`": "`", /* IE allows backtick-delimited attributes?? */ "&": "&" }; - var escape_one = function(c) { + const escape_one = function(c) { return escape_map[c]; }; @@ -34,23 +34,19 @@ Blaze._warn = function (msg) { } }; -var nativeBind = Function.prototype.bind; +const nativeBind = Function.prototype.bind; // An implementation of _.bind which allows better optimization. // See: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments if (nativeBind) { - Blaze._bind = function (func, obj) { + Blaze._bind = function (func, obj, ...rest) { if (arguments.length === 2) { return nativeBind.call(func, obj); } - // Copy the arguments so this function can be optimized. - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } + const args = [obj, ...rest]; - return nativeBind.apply(func, args.slice(1)); + return nativeBind.apply(func, args); }; } else { diff --git a/packages/blaze/render_tests.js b/packages/blaze/render_tests.js index 3b916d783..67df25b72 100644 --- a/packages/blaze/render_tests.js +++ b/packages/blaze/render_tests.js @@ -1,22 +1,22 @@ import { BlazeTools } from 'meteor/blaze-tools'; -var toCode = BlazeTools.toJS; - -var P = HTML.P; -var CharRef = HTML.CharRef; -var DIV = HTML.DIV; -var Comment = HTML.Comment; -var BR = HTML.BR; -var A = HTML.A; -var UL = HTML.UL; -var LI = HTML.LI; -var SPAN = HTML.SPAN; -var HR = HTML.HR; -var TEXTAREA = HTML.TEXTAREA; -var INPUT = HTML.INPUT; - -var materialize = function (content, parent) { - var func = content; +const toCode = BlazeTools.toJS; + +const P = HTML.P; +const CharRef = HTML.CharRef; +const DIV = HTML.DIV; +const Comment = HTML.Comment; +const BR = HTML.BR; +const A = HTML.A; +const UL = HTML.UL; +const LI = HTML.LI; +const SPAN = HTML.SPAN; +const HR = HTML.HR; +const TEXTAREA = HTML.TEXTAREA; +const INPUT = HTML.INPUT; + +const materialize = function (content, parent) { + let func = content; if (typeof content !== 'function') { func = function () { return content; @@ -25,11 +25,11 @@ var materialize = function (content, parent) { Blaze.render(func, parent); }; -var toHTML = Blaze.toHTML; +const toHTML = Blaze.toHTML; Tinytest.add("blaze - render - basic", function (test) { - var run = function (input, expectedInnerHTML, expectedHTML, expectedCode) { - var div = document.createElement("DIV"); + const run = function (input, expectedInnerHTML, expectedHTML, expectedCode) { + const div = document.createElement("DIV"); materialize(input, div); test.equal(canonicalizeHtml(div.innerHTML), expectedInnerHTML); test.equal(toHTML(input), expectedHTML); @@ -105,10 +105,10 @@ Tinytest.add("blaze - render - basic", function (test) { // rather than the 'value' attribute. the 'value' attribute only sets // the initial value. Tinytest.add("blaze - render - input - value", function (test) { - var R = ReactiveVar("hello"); - var div = document.createElement("DIV"); + const R = ReactiveVar("hello"); + const div = document.createElement("DIV"); materialize(INPUT({value: function () { return R.get(); }}), div); - var inputEl = div.querySelector('input'); + const inputEl = div.querySelector('input'); test.equal(inputEl.value, "hello"); inputEl.value = "goodbye"; R.set("hola"); @@ -120,10 +120,10 @@ Tinytest.add("blaze - render - input - value", function (test) { // the 'checked' attribute on input fields of type 'checkbox'. the // 'checked' attribute only sets the initial value. Tinytest.add("blaze - render - input - checked", function (test) { - var R = ReactiveVar(null); - var div = document.createElement("DIV"); + const R = ReactiveVar(null); + const div = document.createElement("DIV"); materialize(INPUT({type: "checkbox", checked: function () { return R.get(); }}), div); - var inputEl = div.querySelector('input'); + const inputEl = div.querySelector('input'); test.equal(inputEl.checked, false); inputEl.checked = true; @@ -135,7 +135,7 @@ Tinytest.add("blaze - render - input - checked", function (test) { }); Tinytest.add("blaze - render - textarea", function (test) { - var run = function (optNode, text, html, code) { + const run = function (optNode, text, html, code) { if (typeof optNode === 'string') { // called with args (text, html, code) code = html; @@ -143,11 +143,11 @@ Tinytest.add("blaze - render - textarea", function (test) { text = optNode; optNode = null; } - var div = document.createElement("DIV"); - var node = TEXTAREA({value: optNode || text}); + const div = document.createElement("DIV"); + const node = TEXTAREA({value: optNode || text}); materialize(node, div); - var value = div.querySelector('textarea').value; + let value = div.querySelector('textarea').value; value = value.replace(/\r\n/g, "\n"); // IE8 substitutes \n with \r\n test.equal(value, text); @@ -181,15 +181,15 @@ Tinytest.add("blaze - render - textarea", function (test) { // test that reactivity of textarea "value" attribute works... (function () { - var R = ReactiveVar('one'); - var div = document.createElement("DIV"); - var node = TEXTAREA({value: function () { + const R = ReactiveVar('one'); + const div = document.createElement("DIV"); + const node = TEXTAREA({value: function () { return Blaze.View(function () { return R.get(); }); }}); materialize(node, div); - var textarea = div.querySelector('textarea'); + const textarea = div.querySelector('textarea'); test.equal(textarea.value, 'one'); R.set('two'); Tracker.flush(); @@ -199,13 +199,13 @@ Tinytest.add("blaze - render - textarea", function (test) { // ... while "content" reactivity simply doesn't update // (but doesn't throw either) (function () { - var R = ReactiveVar('one'); - var div = document.createElement("DIV"); - var node = TEXTAREA([Blaze.View(function () { + const R = ReactiveVar('one'); + const div = document.createElement("DIV"); + const node = TEXTAREA([Blaze.View(function () { return R.get(); })]); materialize(node, div); - var textarea = div.querySelector('textarea'); + const textarea = div.querySelector('textarea'); test.equal(textarea.value, 'one'); R.set('two'); Tracker.flush({_throwFirstError: true}); @@ -217,14 +217,14 @@ Tinytest.add("blaze - render - view isolation", function (test) { // Reactively change a text node (function () { - var R = ReactiveVar('Hello'); - var test1 = function () { + const R = ReactiveVar('Hello'); + const test1 = function () { return P(Blaze.View(function () { return R.get(); })); }; test.equal(toHTML(test1()), '

Hello

'); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); materialize(test1, div); test.equal(canonicalizeHtml(div.innerHTML), "

Hello

"); @@ -235,14 +235,14 @@ Tinytest.add("blaze - render - view isolation", function (test) { // Reactively change an array of text nodes (function () { - var R = ReactiveVar(['Hello', ' World']); - var test1 = function () { + const R = ReactiveVar(['Hello', ' World']); + const test1 = function () { return P(Blaze.View(function () { return R.get(); })); }; test.equal(toHTML(test1()), '

Hello World

'); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); materialize(test1, div); test.equal(canonicalizeHtml(div.innerHTML), "

Hello World

"); @@ -256,8 +256,8 @@ Tinytest.add("blaze - render - view isolation", function (test) { // IE strips malformed styles like "bar::d" from the `style` // attribute. We detect this to adjust expectations for the StyleHandler // test below. -var malformedStylesAllowed = function () { - var div = document.createElement("div"); +const malformedStylesAllowed = function () { + const div = document.createElement("div"); div.setAttribute("style", "bar::d;"); return (div.getAttribute("style") === "bar::d;"); }; @@ -265,10 +265,10 @@ var malformedStylesAllowed = function () { Tinytest.add("blaze - render - view GC", function (test) { // test that removing parent element removes listeners and stops autoruns. (function () { - var R = ReactiveVar('Hello'); - var test1 = P(Blaze.View(function () { return R.get(); })); + const R = ReactiveVar('Hello'); + const test1 = P(Blaze.View(function () { return R.get(); })); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); materialize(test1, div); test.equal(canonicalizeHtml(div.innerHTML), "

Hello

"); @@ -292,10 +292,10 @@ Tinytest.add("blaze - render - view GC", function (test) { Tinytest.add("blaze - render - reactive attributes", function (test) { (function () { - var R = ReactiveVar({'class': ['david gre', CharRef({html: 'ë', str: '\u00eb'}), 'nspan'], + const R = ReactiveVar({'class': ['david gre', CharRef({html: 'ë', str: '\u00eb'}), 'nspan'], id: 'foo'}); - var spanFunc = function () { + const spanFunc = function () { return SPAN(HTML.Attrs( function () { return R.get(); })); }; @@ -305,13 +305,13 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { test.equal(R._numListeners(), 0); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); Blaze.render(spanFunc, div); test.equal(canonicalizeHtml(div.innerHTML), ''); test.equal(R._numListeners(), 1); - var span = div.firstChild; + const span = div.firstChild; test.equal(span.nodeName, 'SPAN'); span.className += ' blah'; // change the element's class outside of Blaze. this simulates what a jQuery could do @@ -331,11 +331,11 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { })(); (function () { - var style = ReactiveVar(false); + const style = ReactiveVar(false); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); - var divFunc = function () { + const divFunc = function () { return DIV({ style: function () { return [Blaze.If(function () { @@ -362,10 +362,10 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { // Test styles. (function () { // Test the case where there is a semicolon in the css attribute. - var R = ReactiveVar({'style': 'foo: "a;aa"; bar: b;', + const R = ReactiveVar({'style': 'foo: "a;aa"; bar: b;', id: 'foo'}); - var spanFunc = function () { + const spanFunc = function () { return SPAN(HTML.Attrs(function () { return R.get(); })); }; @@ -373,12 +373,12 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { test.equal(R._numListeners(), 0); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); Blaze.render(spanFunc, div); test.equal(canonicalizeHtml(div.innerHTML), ''); test.equal(R._numListeners(), 1); - var span = div.firstChild; + const span = div.firstChild; test.equal(span.nodeName, 'SPAN'); span.setAttribute('style', span.getAttribute('style') + '; jquery-style: hidden'); @@ -401,18 +401,18 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { // Test that identical styles are successfully overwritten. (function () { - var R = ReactiveVar({'style': 'foo: a;'}); + const R = ReactiveVar({'style': 'foo: a;'}); - var spanFunc = function () { + const spanFunc = function () { return SPAN(HTML.Attrs(function () { return R.get(); })); }; - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); document.body.appendChild(div); Blaze.render(spanFunc, div); test.equal(canonicalizeHtml(div.innerHTML), ''); - var span = div.firstChild; + const span = div.firstChild; test.equal(span.nodeName, 'SPAN'); span.setAttribute("style", 'foo: b;'); test.equal(canonicalizeHtml(div.innerHTML), ''); @@ -446,7 +446,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { // Test `null`, `undefined`, and `[]` attributes (function () { - var R = ReactiveVar({id: 'foo', + const R = ReactiveVar({id: 'foo', aaa: null, bbb: undefined, ccc: [], @@ -455,7 +455,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { fff: [[]], ggg: ['x', ['y', ['z']]]}); - var spanFunc = function () { + const spanFunc = function () { return SPAN(HTML.Attrs( function () { return R.get(); })); }; @@ -464,9 +464,9 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { test.equal(toCode(SPAN(R.get())), 'HTML.SPAN({id: "foo", ggg: ["x", ["y", ["z"]]]})'); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); Blaze.render(spanFunc, div); - var span = div.firstChild; + const span = div.firstChild; test.equal(span.nodeName, 'SPAN'); test.equal(canonicalizeHtml(div.innerHTML), ''); @@ -490,10 +490,10 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { Tinytest.add("blaze - render - templates and views", function (test) { (function () { - var counter = 1; - var buf = []; + let counter = 1; + const buf = []; - var myTemplate = Blaze.Template( + const myTemplate = Blaze.Template( 'myTemplate', function () { return [String(this.number), @@ -501,15 +501,15 @@ Tinytest.add("blaze - render - templates and views", function (test) { }); myTemplate.constructView = function (number) { - var view = Template.prototype.constructView.call(this); + const view = Template.prototype.constructView.call(this); view.number = number; return view; }; myTemplate.created = function () { test.isFalse(Tracker.active); - var view = this.view; - var parent = Blaze.getView(view, 'myTemplate'); + const view = this.view; + const parent = Blaze.getView(view, 'myTemplate'); if (parent) { buf.push('parent of ' + view.number + ' is ' + parent.number); @@ -520,7 +520,7 @@ Tinytest.add("blaze - render - templates and views", function (test) { myTemplate.onRendered(function () { test.isFalse(Tracker.active); - var nodeDescr = function (node) { + const nodeDescr = function (node) { if (node.nodeType === 8) // comment return ''; if (node.nodeType === 3) // text @@ -529,9 +529,9 @@ Tinytest.add("blaze - render - templates and views", function (test) { return node.nodeName; }; - var view = this.view; - var start = view.firstNode(); - var end = view.lastNode(); + const view = this.view; + let start = view.firstNode(); + let end = view.lastNode(); // skip marker nodes while (start !== end && ! nodeDescr(start)) start = start.nextSibling; @@ -548,14 +548,14 @@ Tinytest.add("blaze - render - templates and views", function (test) { buf.push('destroyed ' + Template.currentData()); }); - var makeView = function () { - var number = counter++; + const makeView = function () { + let number = counter++; return Blaze.With(number, function () { return myTemplate.constructView(number); }); }; - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); Blaze.render(makeView, div); buf.push('---flush---'); @@ -583,7 +583,7 @@ Tinytest.add("blaze - render - templates and views", function (test) { buf.length = 0; counter = 1; - var html = Blaze.toHTML(makeView()); + const html = Blaze.toHTML(makeView()); test.equal(buf, ['created 1', 'parent of 2 is 1', @@ -599,10 +599,10 @@ Tinytest.add("blaze - render - templates and views", function (test) { }); Tinytest.add("blaze - render - findAll", function (test) { - var found = null; - var $found = null; + let found = null; + let $found = null; - var myTemplate = new Template( + const myTemplate = new Template( 'findAllTest', function() { return DIV([P('first'), P('second')]); @@ -612,7 +612,7 @@ Tinytest.add("blaze - render - findAll", function (test) { $found = this.$('p'); }; - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); Blaze.render(myTemplate, div); Tracker.flush(); @@ -624,18 +624,18 @@ Tinytest.add("blaze - render - findAll", function (test) { }); Tinytest.add("blaze - render - reactive attributes 2", function (test) { - var R1 = ReactiveVar(['foo']); - var R2 = ReactiveVar(['bar']); + const R1 = ReactiveVar(['foo']); + const R2 = ReactiveVar(['bar']); - var spanFunc = function () { + const spanFunc = function () { return SPAN(HTML.Attrs( { blah: function () { return R1.get(); } }, function () { return { blah: R2.get() }; })); }; - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); Blaze.render(spanFunc, div); - var check = function (expected) { + const check = function (expected) { test.equal(Blaze.toHTML(spanFunc()), expected); test.equal(canonicalizeHtml(div.innerHTML), expected); }; @@ -682,20 +682,20 @@ Tinytest.add("blaze - render - SVG", function (test) { return; } - var fillColor = ReactiveVar('red'); - var classes = ReactiveVar('one two'); + const fillColor = ReactiveVar('red'); + const classes = ReactiveVar('one two'); - var content = DIV({'class': 'container'}, HTML.SVG( + const content = DIV({'class': 'container'}, HTML.SVG( {width: 100, height: 100}, HTML.CIRCLE({cx: 50, cy: 50, r: 40, stroke: 'black', 'stroke-width': 3, 'class': function () { return classes.get(); }, fill: function () { return fillColor.get(); }}))); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); materialize(content, div); - var circle = div.querySelector('.container > svg > circle'); + const circle = div.querySelector('.container > svg > circle'); test.equal(circle.getAttribute('fill'), 'red'); test.equal(circle.className.baseVal, 'one two'); @@ -711,8 +711,8 @@ Tinytest.add("blaze - render - SVG", function (test) { }); Tinytest.add("ui - attributes", function (test) { - var SPAN = HTML.SPAN; - var amp = HTML.CharRef({html: '&', str: '&'}); + const SPAN = HTML.SPAN; + const amp = HTML.CharRef({html: '&', str: '&'}); test.equal(HTML.toHTML(SPAN({title: ['M', amp, 'Ms']}, 'M', amp, 'M candies')), 'M&M candies'); @@ -722,22 +722,22 @@ if (typeof MutationObserver !== 'undefined') { // This test is not really able to test that Blaze._materializeDOM is called only when // not Blaze._isContentEqual(lastHtmljs, htmljs), which is what we would in fact want to test. Tinytest.addAsync("blaze - render - optimization", function (test, onComplete) { - var R = ReactiveVar('aa'); - var view = Blaze.View(function () { return R.get().substr(0, 1); }); + const R = ReactiveVar('aa'); + const view = Blaze.View(function () { return R.get().substr(0, 1); }); - var renderedCount = 0; + let renderedCount = 0; test.equal(view.renderCount, 0); view._onViewRendered(function () { renderedCount++; }); - var test1 = P(view); + const test1 = P(view); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); - var observedMutations = []; - var observer = new MutationObserver(function (mutations) { + const observedMutations = []; + const observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { observedMutations.push(mutation); }); @@ -745,8 +745,8 @@ if (typeof MutationObserver !== 'undefined') { observer.observe(div, {childList: true, subtree: true}); - var materializeCount = 0; - var originalMaterializeDOM = Blaze._materializeDOM; + let materializeCount = 0; + const originalMaterializeDOM = Blaze._materializeDOM; Blaze._materializeDOM = function (htmljs, intoArray, parentView, _existingWorkStack) { if (parentView === view) { materializeCount++; diff --git a/packages/blaze/template.js b/packages/blaze/template.js index 1c4e852ef..d95624d08 100644 --- a/packages/blaze/template.js +++ b/packages/blaze/template.js @@ -45,9 +45,9 @@ Blaze.Template = function (viewName, renderFunction) { destroyed: [] }; }; -var Template = Blaze.Template; +const Template = Blaze.Template; -var HelperMap = function () {}; +const HelperMap = function () {}; HelperMap.prototype.get = function (name) { return this[' '+name]; }; @@ -107,8 +107,8 @@ Template.prototype.onDestroyed = function (cb) { }; Template.prototype._getCallbacks = function (which) { - var self = this; - var callbacks = self[which] ? [self[which]] : []; + const self = this; + let callbacks = self[which] ? [self[which]] : []; // Fire all callbacks added with the new API (Template.onRendered()) // as well as the old-style callback (e.g. Template.rendered) for // backwards-compatibility. @@ -116,19 +116,19 @@ Template.prototype._getCallbacks = function (which) { return callbacks; }; -var fireCallbacks = function (callbacks, template) { +const fireCallbacks = function (callbacks, template) { Template._withTemplateInstanceFunc( function () { return template; }, function () { - for (var i = 0, N = callbacks.length; i < N; i++) { + for (let i = 0, N = callbacks.length; i < N; i++) { callbacks[i].call(template); } }); }; Template.prototype.constructView = function (contentFunc, elseFunc) { - var self = this; - var view = Blaze.View(self.viewName, self.renderFunction); + const self = this; + const view = Blaze.View(self.viewName, self.renderFunction); view.template = self; view.templateContentBlock = ( @@ -161,7 +161,7 @@ Template.prototype.constructView = function (contentFunc, elseFunc) { view.templateInstance = function () { // Update data, firstNode, and lastNode, and return the TemplateInstance // object. - var inst = view._templateInstance; + const inst = view._templateInstance; /** * @instance @@ -195,7 +195,7 @@ Template.prototype.constructView = function (contentFunc, elseFunc) { // To avoid situations when new callbacks are added in between view // instantiation and event being fired, decide on all callbacks to fire // immediately and then fire them on the event. - var createdCallbacks = self._getCallbacks('created'); + const createdCallbacks = self._getCallbacks('created'); view.onViewCreated(function () { fireCallbacks(createdCallbacks, view.templateInstance()); }); @@ -208,7 +208,7 @@ Template.prototype.constructView = function (contentFunc, elseFunc) { * @locus Client * @deprecated in 1.1 */ - var renderedCallbacks = self._getCallbacks('rendered'); + const renderedCallbacks = self._getCallbacks('rendered'); view.onViewReady(function () { fireCallbacks(renderedCallbacks, view.templateInstance()); }); @@ -221,7 +221,7 @@ Template.prototype.constructView = function (contentFunc, elseFunc) { * @locus Client * @deprecated in 1.1 */ - var destroyedCallbacks = self._getCallbacks('destroyed'); + const destroyedCallbacks = self._getCallbacks('destroyed'); view.onViewDestroyed(function () { fireCallbacks(destroyedCallbacks, view.templateInstance()); }); @@ -294,7 +294,7 @@ Blaze.TemplateInstance = function (view) { * @returns {DOMNode[]} */ Blaze.TemplateInstance.prototype.$ = function (selector) { - var view = this.view; + const view = this.view; if (! view._domrange) throw new Error("Can't use $ on template instance with no DOM"); return view._domrange.$(selector); @@ -317,7 +317,7 @@ Blaze.TemplateInstance.prototype.findAll = function (selector) { * @returns {DOMElement} */ Blaze.TemplateInstance.prototype.find = function (selector) { - var result = this.$(selector); + const result = this.$(selector); return result[0] || null; }; @@ -350,17 +350,17 @@ Blaze.TemplateInstance.prototype.autorun = function (f) { * subscription. */ Blaze.TemplateInstance.prototype.subscribe = function (...args) { - var self = this; + const self = this; - var subHandles = self._subscriptionHandles; + const subHandles = self._subscriptionHandles; // Duplicate logic from Meteor.subscribe - var options = {}; + let options = {}; if (args.length) { - var lastParam = args[args.length - 1]; + const lastParam = args[args.length - 1]; // Match pattern to check if the last arg is an options argument - var lastParamOptionsPattern = { + const lastParamOptionsPattern = { onReady: Match.Optional(Function), // XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use // onStop with an error callback instead. @@ -376,8 +376,8 @@ Blaze.TemplateInstance.prototype.subscribe = function (...args) { } } - var subHandle; - var oldStopped = options.onStop; + let subHandle; + const oldStopped = options.onStop; options.onStop = function (error) { // When the subscription is stopped, remove it from the set of tracked // subscriptions to avoid this list growing without bound @@ -395,9 +395,8 @@ Blaze.TemplateInstance.prototype.subscribe = function (...args) { } }; - var connection = options.connection; - const { onReady, onError, onStop } = options; - var callbacks = { onReady, onError, onStop }; + const { onReady, onError, onStop, connection } = options; + const callbacks = { onReady, onError, onStop }; // The callbacks are passed as the last item in the arguments array passed to // View#subscribe @@ -431,7 +430,7 @@ Blaze.TemplateInstance.prototype.subscribe = function (...args) { */ Blaze.TemplateInstance.prototype.subscriptionsReady = function () { this._allSubsReadyDep.depend(); - this._allSubsReady = Object.values(this._subscriptionHandles).every((handle) => { + this._allSubsReady = Object.values(this._subscriptionHandles).every((handle) => { return handle.ready(); }); @@ -449,12 +448,12 @@ Template.prototype.helpers = function (dict) { throw new Error("Helpers dictionary has to be an object"); } - for (var k in dict) this.__helpers.set(k, dict[k]); + for (let k in dict) this.__helpers.set(k, dict[k]); }; -var canUseGetters = (function () { +const canUseGetters = (function () { if (Object.defineProperty) { - var obj = {}; + let obj = {}; try { Object.defineProperty(obj, "self", { get: function () { return obj; } @@ -472,7 +471,7 @@ if (canUseGetters) { // rather than a value so that not all helpers are implicitly dependent // on the current template instance's `data` property, which would make // them dependent on the data context of the template inclusion. - var currentTemplateInstanceFunc = null; + let currentTemplateInstanceFunc = null; // If getters are supported, define this property with a getter function // to make it effectively read-only, and to work around this bizarre JSC @@ -487,7 +486,7 @@ if (canUseGetters) { if (typeof func !== 'function') { throw new Error("Expected function, got: " + func); } - var oldTmplInstanceFunc = currentTemplateInstanceFunc; + const oldTmplInstanceFunc = currentTemplateInstanceFunc; try { currentTemplateInstanceFunc = templateInstanceFunc; return func(); @@ -503,7 +502,7 @@ if (canUseGetters) { if (typeof func !== 'function') { throw new Error("Expected function, got: " + func); } - var oldTmplInstanceFunc = Template._currentTemplateInstanceFunc; + const oldTmplInstanceFunc = Template._currentTemplateInstanceFunc; try { Template._currentTemplateInstanceFunc = templateInstanceFunc; return func(); @@ -524,20 +523,24 @@ Template.prototype.events = function (eventMap) { throw new Error("Event map has to be an object"); } - var template = this; - var eventMap2 = {}; - for (var k in eventMap) { + const template = this; + let eventMap2 = {}; + for (let k in eventMap) { eventMap2[k] = (function (k, v) { return function (event /*, ...*/) { - var view = this; // passed by EventAugmenter - var data = Blaze.getData(event.currentTarget); - if (data == null) data = {}; - var args = Array.prototype.slice.call(arguments); - var tmplInstanceFunc = Blaze._bind(view.templateInstance, view); - args.splice(1, 0, tmplInstanceFunc()); - - return Template._withTemplateInstanceFunc(tmplInstanceFunc, function () { - return v.apply(data, args); + const view = this; // passed by EventAugmenter + const args = Array.prototype.slice.call(arguments); + // Exiting the current computation to avoid creating unnecessary + // and unexpected reactive dependencies with Templates data + // or any other reactive dependencies defined in event handlers + return Tracker.nonreactive(function () { + let data = Blaze.getData(event.currentTarget); + if (data == null) data = {}; + const tmplInstanceFunc = Blaze._bind(view.templateInstance, view); + args.splice(1, 0, tmplInstanceFunc()); + return Template._withTemplateInstanceFunc(tmplInstanceFunc, function () { + return v.apply(data, args); + }); }); }; })(k, eventMap[k]); diff --git a/packages/blaze/view.js b/packages/blaze/view.js index 185197e62..e3ed33921 100644 --- a/packages/blaze/view.js +++ b/packages/blaze/view.js @@ -32,6 +32,15 @@ /// of name "with". Names are also useful when debugging, so in /// general it's good for functions that create Views to set the name. /// Views associated with templates have names of the form "Template.foo". +import { HTML } from 'meteor/htmljs'; + +/** + * A binding is either `undefined` (pending), `{ error }` (rejected), or + * `{ value }` (resolved). Synchronous values are immediately resolved (i.e., + * `{ value }` is used). The other states are reserved for asynchronous bindings + * (i.e., values wrapped with `Promise`s). + * @typedef {{ error: unknown } | { value: unknown } | undefined} Binding + */ /** * @class @@ -81,6 +90,7 @@ Blaze.View = function (name, render) { this._hasGeneratedParent = false; // Bindings accessible to children views (via view.lookup('name')) within the // closest template view. + /** @type {Record>} */ this._scopeBindings = {}; this.renderCount = 0; @@ -99,8 +109,8 @@ Blaze.View.prototype._onViewRendered = function (cb) { }; Blaze.View.prototype.onViewReady = function (cb) { - var self = this; - var fire = function () { + const self = this; + const fire = function () { Tracker.afterFlush(function () { if (! self.isDestroyed) { Blaze._withCurrentView(self, function () { @@ -124,10 +134,10 @@ Blaze.View.prototype.onViewDestroyed = function (cb) { this._callbacks.destroyed.push(cb); }; Blaze.View.prototype.removeViewDestroyedListener = function (cb) { - var destroyed = this._callbacks.destroyed; + const destroyed = this._callbacks.destroyed; if (! destroyed) return; - var index = destroyed.lastIndexOf(cb); + const index = destroyed.lastIndexOf(cb); if (index !== -1) { // XXX You'd think the right thing to do would be splice, but _fireCallbacks // gets sad if you remove callbacks while iterating over the list. Should @@ -157,7 +167,7 @@ Blaze.View.prototype.removeViewDestroyedListener = function (cb) { /// from either onViewCreated (guarded against the absence of /// view._domrange), or onViewReady. Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) { - var self = this; + const self = this; // The restrictions on when View#autorun can be called are in order // to avoid bad patterns, like creating a Blaze.View and immediately @@ -187,9 +197,9 @@ Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) { throw new Error("Can't call View#autorun from inside render(); try calling it from the created or rendered callback"); } - var templateInstanceFunc = Blaze.Template._currentTemplateInstanceFunc; + const templateInstanceFunc = Blaze.Template._currentTemplateInstanceFunc; - var func = function viewAutorun(c) { + const func = function viewAutorun(c) { return Blaze._withCurrentView(_inViewScope || self, function () { return Blaze.Template._withTemplateInstanceFunc( templateInstanceFunc, function () { @@ -203,9 +213,9 @@ Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) { // and Firefox prefer it in debuggers over the name function was declared by. func.displayName = (self.name || 'anonymous') + ':' + (displayName || 'anonymous'); - var comp = Tracker.autorun(func); + const comp = Tracker.autorun(func); - var stopComputation = function () { comp.stop(); }; + const stopComputation = function () { comp.stop(); }; self.onViewDestroyed(stopComputation); comp.onStop(function () { self.removeViewDestroyedListener(stopComputation); @@ -215,7 +225,7 @@ Blaze.View.prototype.autorun = function (f, _inViewScope, displayName) { }; Blaze.View.prototype._errorIfShouldntCallSubscribe = function () { - var self = this; + const self = this; if (! self.isCreated) { throw new Error("View#subscribe must be called from the created callback at the earliest"); @@ -235,12 +245,12 @@ Blaze.View.prototype._errorIfShouldntCallSubscribe = function () { * see if it is ready, or stop it manually */ Blaze.View.prototype.subscribe = function (args, options) { - var self = this; + const self = this; options = options || {}; self._errorIfShouldntCallSubscribe(); - var subHandle; + let subHandle; if (options.connection) { subHandle = options.connection.subscribe.apply(options.connection, args); } else { @@ -271,8 +281,8 @@ Blaze.View.prototype.lastNode = function () { Blaze._fireCallbacks = function (view, which) { Blaze._withCurrentView(view, function () { Tracker.nonreactive(function fireCallbacks() { - var cbs = view._callbacks[which]; - for (var i = 0, N = (cbs && cbs.length); i < N; i++) + const cbs = view._callbacks[which]; + for (let i = 0, N = (cbs && cbs.length); i < N; i++) cbs[i] && cbs[i].call(view); }); }); @@ -290,14 +300,14 @@ Blaze._createView = function (view, parentView, forExpansion) { Blaze._fireCallbacks(view, 'created'); }; -var doFirstRender = function (view, initialContent) { - var domrange = new Blaze._DOMRange(initialContent); +const doFirstRender = function (view, initialContent) { + const domrange = new Blaze._DOMRange(initialContent); view._domrange = domrange; domrange.view = view; view.isRendered = true; Blaze._fireCallbacks(view, 'rendered'); - var teardownHook = null; + let teardownHook = null; domrange.onAttached(function attached(range, element) { view._isAttached = true; @@ -310,7 +320,7 @@ var doFirstRender = function (view, initialContent) { // tear down the teardown hook view.onViewDestroyed(function () { - teardownHook && teardownHook.stop(); + if (teardownHook) teardownHook.stop(); teardownHook = null; }); @@ -334,24 +344,24 @@ var doFirstRender = function (view, initialContent) { Blaze._materializeView = function (view, parentView, _workStack, _intoArray) { Blaze._createView(view, parentView); - var domrange; - var lastHtmljs; + let domrange; + let lastHtmljs; // We don't expect to be called in a Computation, but just in case, // wrap in Tracker.nonreactive. Tracker.nonreactive(function () { view.autorun(function doRender(c) { // `view.autorun` sets the current view. - view.renderCount++; + view.renderCount = view.renderCount + 1; view._isInRender = true; // Any dependencies that should invalidate this Computation come // from this line: - var htmljs = view._render(); + const htmljs = view._render(); view._isInRender = false; if (! c.firstRun && ! Blaze._isContentEqual(lastHtmljs, htmljs)) { Tracker.nonreactive(function doMaterialize() { // re-render - var rangesAndNodes = Blaze._materializeDOM(htmljs, [], view); + const rangesAndNodes = Blaze._materializeDOM(htmljs, [], view); domrange.setMembers(rangesAndNodes); Blaze._fireCallbacks(view, 'rendered'); }); @@ -370,7 +380,7 @@ Blaze._materializeView = function (view, parentView, _workStack, _intoArray) { }, undefined, 'materialize'); // first render. lastHtmljs is the first htmljs. - var initialContents; + let initialContents; if (! _workStack) { initialContents = Blaze._materializeDOM(lastHtmljs, [], view); domrange = doFirstRender(view, initialContents); @@ -416,12 +426,12 @@ Blaze._expandView = function (view, parentView) { Blaze._createView(view, parentView, true /*forExpansion*/); view._isInRender = true; - var htmljs = Blaze._withCurrentView(view, function () { + const htmljs = Blaze._withCurrentView(view, function () { return view._render(); }); view._isInRender = false; - var result = Blaze._expand(htmljs, view); + const result = Blaze._expand(htmljs, view); if (Tracker.active) { Tracker.onInvalidate(function () { @@ -467,8 +477,8 @@ Blaze._HTMLJSExpander.def({ // Return Blaze.currentView, but only if it is being rendered // (i.e. we are in its render() method). -var currentViewIfRendering = function () { - var view = Blaze.currentView; +const currentViewIfRendering = function () { + const view = Blaze.currentView; return (view && view._isInRender) ? view : null; }; @@ -480,8 +490,9 @@ Blaze._expand = function (htmljs, parentView) { Blaze._expandAttributes = function (attrs, parentView) { parentView = parentView || currentViewIfRendering(); - return (new Blaze._HTMLJSExpander( + const expanded = (new Blaze._HTMLJSExpander( {parentView: parentView})).visitAttributes(attrs); + return expanded || {}; }; Blaze._destroyView = function (view, _skipNodes) { @@ -500,7 +511,7 @@ Blaze._destroyView = function (view, _skipNodes) { // otherwise it's tracker.flush will cause the above line will // not be called and their views won't be destroyed // Involved issues: DOMRange "Must be attached" error, mem leak - + Blaze._fireCallbacks(view, 'destroyed'); }; @@ -531,8 +542,14 @@ Blaze._isContentEqual = function (a, b) { */ Blaze.currentView = null; +/** + * @template T + * @param {Blaze.View} view + * @param {function(): T} func + * @returns {T} + */ Blaze._withCurrentView = function (view, func) { - var oldView = Blaze.currentView; + const oldView = Blaze.currentView; try { Blaze.currentView = view; return func(); @@ -545,7 +562,7 @@ Blaze._withCurrentView = function (view, func) { // Privately, it takes any HTMLJS (extended with Views and Templates) // except null or undefined, or a function that returns any extended // HTMLJS. -var checkRenderContent = function (content) { +const checkRenderContent = function (content) { if (content === null) throw new Error("Can't render null"); if (typeof content === 'undefined') @@ -570,7 +587,7 @@ var checkRenderContent = function (content) { // For Blaze.render and Blaze.toHTML, take content and // wrap it in a View, unless it's a single View or // Template already. -var contentAsView = function (content) { +const contentAsView = function (content) { checkRenderContent(content); if (content instanceof Blaze.Template) { @@ -578,7 +595,7 @@ var contentAsView = function (content) { } else if (content instanceof Blaze.View) { return content; } else { - var func = content; + let func = content; if (typeof func !== 'function') { func = function () { return content; @@ -591,7 +608,7 @@ var contentAsView = function (content) { // For Blaze.renderWithData and Blaze.toHTMLWithData, wrap content // in a function, if necessary, so it can be a content arg to // a Blaze.With. -var contentAsFunc = function (content) { +const contentAsFunc = function (content) { checkRenderContent(content); if (typeof content !== 'function') { @@ -635,7 +652,7 @@ Blaze.render = function (content, parentElement, nextNode, parentView) { parentView = parentView || currentViewIfRendering(); - var view = contentAsView(content); + const view = contentAsView(content); // TODO: this is only needed in development if (!parentView) { @@ -644,7 +661,7 @@ Blaze.render = function (content, parentElement, nextNode, parentView) { }); view.onViewDestroyed(function () { - var index = Blaze.__rootViews.indexOf(view); + let index = Blaze.__rootViews.indexOf(view); if (index > -1) { Blaze.__rootViews.splice(index, 1); } @@ -696,7 +713,7 @@ Blaze.remove = function (view) { while (view) { if (! view.isDestroyed) { - var range = view._domrange; + const range = view._domrange; range.destroy(); if (range.attached && ! range.parentRange) { @@ -759,12 +776,12 @@ Blaze._toText = function (htmljs, parentView, textMode) { * @param {DOMElement|Blaze.View} [elementOrView] Optional. An element that was rendered by a Meteor, or a View. */ Blaze.getData = function (elementOrView) { - var theWith; + let theWith; if (! elementOrView) { theWith = Blaze.getView('with'); } else if (elementOrView instanceof Blaze.View) { - var view = elementOrView; + const view = elementOrView; theWith = (view.name === 'with' ? view : Blaze.getView(view, 'with')); } else if (typeof elementOrView.nodeType === 'number') { @@ -775,7 +792,7 @@ Blaze.getData = function (elementOrView) { throw new Error("Expected DOM element or View"); } - return theWith ? theWith.dataVar.get() : null; + return theWith ? theWith.dataVar.get()?.value : null; }; // For back-compat @@ -797,7 +814,7 @@ Blaze.getElementData = function (element) { * @param {DOMElement} [element] Optional. If specified, the View enclosing `element` is returned. */ Blaze.getView = function (elementOrView, _viewName) { - var viewName = _viewName; + let viewName = _viewName; if ((typeof elementOrView) === 'string') { // omitted elementOrView; viewName present @@ -821,7 +838,7 @@ Blaze.getView = function (elementOrView, _viewName) { // Gets the current view or its nearest ancestor of name // `name`. Blaze._getCurrentView = function (name) { - var view = Blaze.currentView; + let view = Blaze.currentView; // Better to fail in cases where it doesn't make sense // to use Blaze._getCurrentView(). There will be a current // view anywhere it does. You can check Blaze.currentView @@ -841,7 +858,7 @@ Blaze._getCurrentView = function (name) { }; Blaze._getParentView = function (view, name) { - var v = view.parentView; + let v = view.parentView; if (name) { while (v && v.name !== name) @@ -852,8 +869,8 @@ Blaze._getParentView = function (view, name) { }; Blaze._getElementView = function (elem, name) { - var range = Blaze._DOMRange.forElement(elem); - var view = null; + let range = Blaze._DOMRange.forElement(elem); + let view = null; while (range && ! view) { view = (range.view || null); if (! view) { @@ -875,7 +892,7 @@ Blaze._getElementView = function (elem, name) { Blaze._addEventMap = function (view, eventMap, thisInHandler) { thisInHandler = (thisInHandler || null); - var handles = []; + const handles = []; if (! view._domrange) throw new Error("View must have a DOMRange"); @@ -883,22 +900,22 @@ Blaze._addEventMap = function (view, eventMap, thisInHandler) { view._domrange.onAttached(function attached_eventMaps(range, element) { Object.keys(eventMap).forEach(function (spec) { let handler = eventMap[spec]; - var clauses = spec.split(/,\s+/); + const clauses = spec.split(/,\s+/); // iterate over clauses of spec, e.g. ['click .foo', 'click .bar'] clauses.forEach(function (clause) { - var parts = clause.split(/\s+/); + const parts = clause.split(/\s+/); if (parts.length === 0) return; - var newEvents = parts.shift(); - var selector = parts.join(' '); + const newEvents = parts.shift(); + const selector = parts.join(' '); handles.push(Blaze._EventSupport.listen( element, newEvents, selector, function (evt) { if (! range.containsElement(evt.currentTarget, selector, newEvents)) return null; - var handlerThis = thisInHandler || this; - var handlerArgs = arguments; + const handlerThis = thisInHandler || this; + const handlerArgs = arguments; return Blaze._withCurrentView(view, function () { return handler.apply(handlerThis, handlerArgs); }); diff --git a/packages/blaze/view_tests.js b/packages/blaze/view_tests.js index 2e49b4240..d1bd67ef7 100644 --- a/packages/blaze/view_tests.js +++ b/packages/blaze/view_tests.js @@ -1,11 +1,11 @@ if (Meteor.isClient) { Tinytest.add("blaze - view - callbacks", function (test) { - var R = ReactiveVar('foo'); + const R = ReactiveVar('foo'); - var buf = ''; + let buf = ''; - var v = Blaze.View(function () { + const v = Blaze.View(function () { return R.get(); }); @@ -24,7 +24,7 @@ if (Meteor.isClient) { test.equal(buf, ''); - var div = document.createElement("DIV"); + const div = document.createElement("DIV"); test.isFalse(v.isRendered); test.isFalse(v._isAttached); test.equal(canonicalizeHtml(div.innerHTML), ""); @@ -57,10 +57,10 @@ if (Meteor.isClient) { }); // this checks, whether a DOMRange is correctly marked as - // desroyed after Blaze.remove has destroyed + // desroyed after Blaze.remove has destroyed // the corresponding view Tinytest.add("blaze - view - destroy", function (test) { - var v = { + const v = { _domrange: Blaze._DOMRange([]) }; v._domrange.view = Blaze.View(); @@ -68,12 +68,12 @@ if (Meteor.isClient) { Blaze.remove(v); test.equal(v._domrange.view.isDestroyed, true); }); - + // this checks, whether an unattached DOMRange notifies // correctly about it's root cause, when throwing due to an event Tinytest.add("blaze - view - attached", function (test) { - test.throws(() => Blaze._DOMRange.prototype.containsElement.call({attached: false, view: {name: 'Template.foo'}}, undefined, '.class', 'click'), - `click event triggerd with .class on foo but associated view is not be found. + test.throws(() => Blaze._DOMRange.prototype.containsElement.call({attached: false, view: {name: 'Template.foo'}}, undefined, '.class', 'click'), + `click event triggered with .class on foo but associated view is not be found. Make sure the event doesn't destroy the view.`); }); } diff --git a/packages/caching-html-compiler/.npm/package/npm-shrinkwrap.json b/packages/caching-html-compiler/.npm/package/npm-shrinkwrap.json index c2f88d4f7..eccd35bc3 100644 --- a/packages/caching-html-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/caching-html-compiler/.npm/package/npm-shrinkwrap.json @@ -1,10 +1,10 @@ { - "lockfileVersion": 1, + "lockfileVersion": 4, "dependencies": { "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" } } } diff --git a/packages/caching-html-compiler/.versions b/packages/caching-html-compiler/.versions index ee1912f0d..617c233bb 100644 --- a/packages/caching-html-compiler/.versions +++ b/packages/caching-html-compiler/.versions @@ -1,23 +1,24 @@ -babel-compiler@7.6.1 -babel-runtime@1.5.0 -blaze-tools@1.1.2 -caching-compiler@1.2.2 -caching-html-compiler@1.2.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -fetch@0.1.1 -html-tools@1.1.2 -htmljs@1.1.0 -inter-process-messaging@0.1.1 -meteor@1.9.3 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -spacebars-compiler@1.3.0 -templating-tools@1.2.1 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +blaze-tools@2.0.0 +caching-compiler@2.0.0 +caching-html-compiler@2.0.0 +core-runtime@1.0.0 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +fetch@0.1.5 +html-tools@2.0.0 +htmljs@2.0.1 +inter-process-messaging@0.1.2 +meteor@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +spacebars-compiler@2.0.0 +templating-tools@2.0.0 diff --git a/packages/caching-html-compiler/caching-html-compiler.js b/packages/caching-html-compiler/caching-html-compiler.js index c6b714e4a..7661ff1c9 100644 --- a/packages/caching-html-compiler/caching-html-compiler.js +++ b/packages/caching-html-compiler/caching-html-compiler.js @@ -1,9 +1,12 @@ +/* global TemplatingTools CachingCompiler */ +// eslint-disable-next-line import/no-unresolved import isEmpty from 'lodash.isempty'; -const path = Plugin.path; +const { path } = Plugin; // The CompileResult type for this CachingCompiler is the return value of // htmlScanner.scan: a {js, head, body, bodyAttrs} object. +// eslint-disable-next-line no-undef CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { /** * Constructor for CachingHtmlCompiler @@ -18,7 +21,7 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { constructor(name, tagScannerFunc, tagHandlerFunc) { super({ compilerName: name, - defaultCacheSize: 1024*1024*10, + defaultCacheSize: 1024 * 1024 * 10, }); this._bodyAttrInfo = null; @@ -28,12 +31,13 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { } // Implements method from CachingCompilerBase + // eslint-disable-next-line class-methods-use-this compileResultSize(compileResult) { - function lengthOrZero(field) { - return field ? field.length : 0; - } - return lengthOrZero(compileResult.head) + lengthOrZero(compileResult.body) + - lengthOrZero(compileResult.js); + const lengthOrZero = (field) => field ? field.length : 0; + const headSize = lengthOrZero(compileResult.head); + const bodySize = lengthOrZero(compileResult.body); + const jsSize = lengthOrZero(compileResult.js); + return headSize + bodySize + jsSize; } // Overrides method from CachingCompiler @@ -43,13 +47,14 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { } // Implements method from CachingCompilerBase + // eslint-disable-next-line class-methods-use-this getCacheKey(inputFile) { // Note: the path is only used for errors, so it doesn't have to be part // of the cache key. return [ inputFile.getArch(), inputFile.getSourceHash(), - inputFile.hmrAvailable && inputFile.hmrAvailable() + inputFile.hmrAvailable && inputFile.hmrAvailable(), ]; } @@ -60,8 +65,8 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { try { const tags = this.tagScannerFunc({ sourceName: inputPath, - contents: contents, - tagNames: ["body", "head", "template"] + contents, + tagNames: ['body', 'head', 'template'], }); return this.tagHandlerFunc(tags, inputFile.hmrAvailable && inputFile.hmrAvailable()); @@ -69,25 +74,24 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { if (e instanceof TemplatingTools.CompileError) { inputFile.error({ message: e.message, - line: e.line + line: e.line, }); return null; - } else { - throw e; } + throw e; } } // Implements method from CachingCompilerBase addCompileResult(inputFile, compileResult) { - let allJavaScript = ""; + let allJavaScript = ''; if (compileResult.head) { - inputFile.addHtml({ section: "head", data: compileResult.head }); + inputFile.addHtml({ section: 'head', data: compileResult.head }); } if (compileResult.body) { - inputFile.addHtml({ section: "body", data: compileResult.body }); + inputFile.addHtml({ section: 'body', data: compileResult.body }); } if (compileResult.js) { @@ -97,19 +101,19 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { if (!isEmpty(compileResult.bodyAttrs)) { Object.keys(compileResult.bodyAttrs).forEach((attr) => { const value = compileResult.bodyAttrs[attr]; - if (this._bodyAttrInfo.hasOwnProperty(attr) && + if (Object.prototype.hasOwnProperty.call(this._bodyAttrInfo, attr) && this._bodyAttrInfo[attr].value !== value) { // two conflicting attributes on tags in two different template // files inputFile.error({ message: - ` declarations have conflicting values for the '${ attr }' ` + - `attribute in the following files: ` + - this._bodyAttrInfo[attr].inputFile.getPathInPackage() + - `, ${ inputFile.getPathInPackage() }` + `${` declarations have conflicting values for the '${attr}' ` + + 'attribute in the following files: '}${ + this._bodyAttrInfo[attr].inputFile.getPathInPackage() + }, ${inputFile.getPathInPackage()}`, }); } else { - this._bodyAttrInfo[attr] = {inputFile, value}; + this._bodyAttrInfo[attr] = { inputFile, value }; } }); @@ -123,25 +127,23 @@ CachingHtmlCompiler = class CachingHtmlCompiler extends CachingCompiler { }); `; } - + if (allJavaScript) { const filePath = inputFile.getPathInPackage(); // XXX this path manipulation may be unnecessarily complex let pathPart = path.dirname(filePath); - if (pathPart === '.') - pathPart = ''; - if (pathPart.length && pathPart !== path.sep) - pathPart = pathPart + path.sep; + if (pathPart === '.') pathPart = ''; + if (pathPart.length && pathPart !== path.sep) pathPart += path.sep; const ext = path.extname(filePath); const basename = path.basename(filePath, ext); // XXX generate a source map inputFile.addJavaScript({ - path: path.join(pathPart, "template." + basename + ".js"), - data: allJavaScript + path: path.join(pathPart, `template.${basename}.js`), + data: allJavaScript, }); } } -} +}; diff --git a/packages/caching-html-compiler/package.js b/packages/caching-html-compiler/package.js index a1eb91e66..e920eee6b 100644 --- a/packages/caching-html-compiler/package.js +++ b/packages/caching-html-compiler/package.js @@ -1,27 +1,24 @@ +/* eslint-env meteor */ Package.describe({ name: 'caching-html-compiler', - summary: "Pluggable class for compiling HTML into templates", - version: '1.2.1', - git: 'https://github.com/meteor/blaze.git' + summary: 'Pluggable class for compiling HTML into templates', + version: '2.0.0', + git: 'https://github.com/meteor/blaze.git', }); Npm.depends({ - 'lodash.isempty': '4.4.0' + 'lodash.isempty': '4.4.0', }); Package.onUse(function(api) { api.use([ - 'caching-compiler@1.2.2', - 'ecmascript@0.15.1' + 'caching-compiler@2.0.0', + 'ecmascript@0.16.9', ]); api.export('CachingHtmlCompiler', 'server'); - api.use([ - 'templating-tools@1.2.1' - ]); + api.use(['templating-tools@2.0.0']); - api.addFiles([ - 'caching-html-compiler.js' - ], 'server'); + api.addFiles(['caching-html-compiler.js'], 'server'); }); diff --git a/packages/html-tools/.versions b/packages/html-tools/.versions index 7f7cd197f..23edeac82 100644 --- a/packages/html-tools/.versions +++ b/packages/html-tools/.versions @@ -1,51 +1,54 @@ -allow-deny@1.1.0 -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -binary-heap@1.0.11 -blaze-tools@1.0.10 -boilerplate-generator@1.7.1 -callback-hook@1.3.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.4.0 -ddp-common@1.4.0 -ddp-server@2.3.2 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -ejson@1.1.1 -fetch@0.1.1 -geojson-utils@1.0.10 -html-tools@1.1.2 -htmljs@1.1.0 -id-map@1.1.0 -inter-process-messaging@0.1.1 -local-test:html-tools@1.1.2 -logging@1.2.0 -meteor@1.9.3 -minimongo@1.6.2 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo@1.11.0 -mongo-decimal@0.1.2 -mongo-dev-server@1.1.0 -mongo-id@1.0.7 -npm-mongo@3.9.0 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.0 -socket-stream-client@0.3.1 -tinytest@1.1.0 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.10.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +blaze-tools@2.0.0 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.2 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.0 +ddp-common@1.4.3 +ddp-server@3.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +html-tools@2.0.0 +htmljs@2.0.1 +id-map@1.2.0 +inter-process-messaging@0.1.2 +local-test:html-tools@2.0.0 +logging@1.3.5 +meteor@2.0.0 +minimongo@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo@2.0.0 +mongo-decimal@0.1.4-beta300.7 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.3 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +socket-stream-client@0.5.3 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.0 +webapp-hashing@1.1.2 diff --git a/packages/html-tools/package.js b/packages/html-tools/package.js index af5d8d183..e9d0da6ce 100644 --- a/packages/html-tools/package.js +++ b/packages/html-tools/package.js @@ -1,14 +1,14 @@ Package.describe({ name: 'html-tools', summary: "Standards-compliant HTML tools", - version: '1.1.3', + version: '2.0.0', git: 'https://github.com/meteor/blaze.git' }); Package.onUse(function (api) { - api.use('ecmascript@0.15.1'); - api.use('htmljs@1.1.1'); - api.imply('htmljs@1.1.1'); + api.use('ecmascript@0.16.9'); + api.use('htmljs@2.0.1'); + api.imply('htmljs@2.0.1'); api.export('HTMLTools'); api.mainModule('main.js'); @@ -16,10 +16,10 @@ Package.onUse(function (api) { Package.onTest(function (api) { api.use('ecmascript'); - api.use('tinytest@1.1.0'); + api.use('tinytest'); api.use('html-tools'); - api.use('htmljs@1.1.1'); + api.use('htmljs@2.0.1'); api.use('blaze-tools'); // for `toJS` api.addFiles([ diff --git a/packages/htmljs/.versions b/packages/htmljs/.versions index 92f76f68b..0545ce048 100644 --- a/packages/htmljs/.versions +++ b/packages/htmljs/.versions @@ -1,49 +1,52 @@ -allow-deny@1.1.0 -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -binary-heap@1.0.11 -boilerplate-generator@1.7.1 -callback-hook@1.3.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.4.0 -ddp-common@1.4.0 -ddp-server@2.3.2 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -ejson@1.1.1 -fetch@0.1.1 -geojson-utils@1.0.10 -htmljs@1.1.1 -id-map@1.1.0 -inter-process-messaging@0.1.1 -local-test:htmljs@1.1.1 -logging@1.2.0 -meteor@1.9.3 -minimongo@1.6.2 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo@1.11.0 -mongo-decimal@0.1.2 -mongo-dev-server@1.1.0 -mongo-id@1.0.7 -npm-mongo@3.9.0 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.0 -socket-stream-client@0.3.1 -tinytest@1.1.0 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.10.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.2 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.0 +ddp-common@1.4.3 +ddp-server@3.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +htmljs@2.0.1 +id-map@1.2.0 +inter-process-messaging@0.1.2 +local-test:htmljs@2.0.1 +logging@1.3.5 +meteor@2.0.0 +minimongo@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo@2.0.0 +mongo-decimal@0.1.4-beta300.7 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.3 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +socket-stream-client@0.5.3 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.0 +webapp-hashing@1.1.2 diff --git a/packages/htmljs/package.js b/packages/htmljs/package.js index 1b9c26bc5..ca143bf32 100644 --- a/packages/htmljs/package.js +++ b/packages/htmljs/package.js @@ -1,12 +1,12 @@ Package.describe({ name: 'htmljs', summary: "Small library for expressing HTML trees", - version: '1.1.1', + version: '2.0.1', git: 'https://github.com/meteor/blaze.git' }); Package.onUse(function (api) { - api.use('ecmascript@0.15.1'); + api.use('ecmascript@0.16.9'); api.export('HTML'); api.mainModule('preamble.js'); @@ -14,7 +14,7 @@ Package.onUse(function (api) { Package.onTest(function (api) { api.use('ecmascript'); - api.use('tinytest@1.1.0'); + api.use('tinytest'); api.use('htmljs'); diff --git a/packages/htmljs/visitors.js b/packages/htmljs/visitors.js index f5a94623d..f846eaf0a 100644 --- a/packages/htmljs/visitors.js +++ b/packages/htmljs/visitors.js @@ -10,6 +10,7 @@ import { isVoidElement, } from './html'; +const isPromiseLike = x => !!x && typeof x.then === 'function'; var IDENTITY = function (x) { return x; }; @@ -156,6 +157,11 @@ TransformingVisitor.def({ // an array, or in some uses, a foreign object (such as // a template tag). visitAttributes: function (attrs, ...args) { + // Allow Promise-like values here; these will be handled in materializer. + if (isPromiseLike(attrs)) { + return attrs; + } + if (isArray(attrs)) { var result = attrs; for (var i = 0; i < attrs.length; i++) { diff --git a/packages/observe-sequence/.versions b/packages/observe-sequence/.versions index 708081f5c..da70a9529 100644 --- a/packages/observe-sequence/.versions +++ b/packages/observe-sequence/.versions @@ -1,49 +1,52 @@ -allow-deny@1.1.0 -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -binary-heap@1.0.11 -boilerplate-generator@1.7.1 -callback-hook@1.3.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.4.1 -ddp-common@1.4.0 -ddp-server@2.3.3 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.1 -ecmascript-runtime-server@0.10.1 -ejson@1.1.1 -fetch@0.1.1 -geojson-utils@1.0.10 -id-map@1.1.1 -inter-process-messaging@0.1.1 -local-test:observe-sequence@1.0.19 -logging@1.2.0 -meteor@1.9.3 -minimongo@1.6.2 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo@1.11.1 -mongo-decimal@0.1.2 -mongo-dev-server@1.1.0 -mongo-id@1.0.8 -npm-mongo@3.9.0 -observe-sequence@1.0.19 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.1 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.0 -socket-stream-client@0.3.3 -tinytest@1.1.0 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.10.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.2 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.0 +ddp-common@1.4.3 +ddp-server@3.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +id-map@1.2.0 +inter-process-messaging@0.1.2 +local-test:observe-sequence@2.0.0 +logging@1.3.5 +meteor@2.0.0 +minimongo@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo@2.0.0 +mongo-decimal@0.1.4-beta300.7 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.3 +observe-sequence@2.0.0 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +socket-stream-client@0.5.3 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.0 +webapp-hashing@1.1.2 diff --git a/packages/observe-sequence/observe_sequence_tests.js b/packages/observe-sequence/observe_sequence_tests.js index 7f62a0f47..c10afb94f 100644 --- a/packages/observe-sequence/observe_sequence_tests.js +++ b/packages/observe-sequence/observe_sequence_tests.js @@ -12,7 +12,7 @@ // @param expectedCallbacks {Array} // elements are objects eg {addedAt: [array of arguments]} // @param numExpectedWarnings {Number} -runOneObserveSequenceTestCase = function (test, sequenceFunc, +runOneObserveSequenceTestCase = async function (test, sequenceFunc, run, expectedCallbacks, numExpectedWarnings) { if (numExpectedWarnings) @@ -53,7 +53,7 @@ runOneObserveSequenceTestCase = function (test, sequenceFunc, } }); - run(); + await run(); Tracker.flush(); handle.stop(); @@ -476,7 +476,7 @@ Tinytest.add('observe-sequence - cursor to null', function (test) { Tinytest.add('observe-sequence - cursor to array', function (test) { var dep = new Tracker.Dependency; var coll = new Mongo.Collection(null); - coll.insert({_id: "13", foo: 1}); + coll.insert({_id: "13.5", foo: 1}); var cursor = coll.find({}, {sort: {_id: 1}}); var seq = cursor; @@ -485,14 +485,14 @@ Tinytest.add('observe-sequence - cursor to array', function (test) { return seq; }, function () { coll.insert({_id: "37", bar: 2}); - seq = [{_id: "13", foo: 1}, {_id: "38", bar: 2}]; + seq = [{_id: "13.5", foo: 1}, {_id: "38", bar: 2}]; dep.changed(); }, [ - {addedAt: ["13", {_id: "13", foo: 1}, 0, null]}, + {addedAt: ["13.5", {_id: "13.5", foo: 1}, 0, null]}, {addedAt: ["37", {_id: "37", bar: 2}, 1, null]}, {removedAt: ["37", {_id: "37", bar: 2}, 1]}, {addedAt: ["38", {_id: "38", bar: 2}, 1, null]}, - {changedAt: ["13", {_id: "13", foo: 1}, {_id: "13", foo: 1}, 0]} + {changedAt: ["13.5", {_id: "13.5", foo: 1}, {_id: "13.5", foo: 1}, 0]} ]); }); @@ -504,13 +504,13 @@ Tinytest.add('observe-sequence - cursor', function (test) { runOneObserveSequenceTestCase(test, function () { return seq; - }, function () { - coll.insert({_id: "37", rank: 2}); - coll.insert({_id: "77", rank: 3}); - coll.remove({_id: "37"}); // should fire a 'removedAt' callback - coll.insert({_id: "11", rank: 0}); // should fire an 'addedAt' callback - coll.update({_id: "13"}, {$set: {updated: true}}); // should fire an 'changedAt' callback - coll.update({_id: "77"}, {$set: {rank: -1}}); // should fire 'changedAt' and 'movedTo' callback + }, async function () { + await coll.insertAsync({_id: "37", rank: 2}); + await coll.insertAsync({_id: "77", rank: 3}); + await coll.removeAsync({_id: "37"}); // should fire a 'removedAt' callback + await coll.insertAsync({_id: "11", rank: 0}); // should fire an 'addedAt' callback + await coll.updateAsync({_id: "13"}, {$set: {updated: true}}); // should fire an 'changedAt' callback + await coll.updateAsync({_id: "77"}, {$set: {rank: -1}}); // should fire 'changedAt' and 'movedTo' callback }, [ // this case must not fire spurious calls as the array to array // case does. otherwise, the entire power of cursors is lost in diff --git a/packages/observe-sequence/package.js b/packages/observe-sequence/package.js index 60d37d642..3b8369558 100644 --- a/packages/observe-sequence/package.js +++ b/packages/observe-sequence/package.js @@ -1,13 +1,14 @@ Package.describe({ summary: "Observe changes to various sequence types such as arrays, cursors and objects", - version: "1.0.21" + version: '2.0.0', }); Package.onUse(function (api) { - api.use('tracker@1.2.0'); + api.use('tracker@1.3.2'); api.use('mongo-id@1.0.8'); // for idStringify - api.use('diff-sequence@1.1.1'); - api.use('random@1.2.0'); + api.use('diff-sequence@1.1.2'); + api.use('random@1.2.1'); + api.use('ecmascript@0.16.9'); api.export('ObserveSequence'); api.addFiles(['observe_sequence.js']); }); diff --git a/packages/spacebars-compiler/.npm/package/npm-shrinkwrap.json b/packages/spacebars-compiler/.npm/package/npm-shrinkwrap.json index f9df4ac62..9e1dfc699 100644 --- a/packages/spacebars-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/spacebars-compiler/.npm/package/npm-shrinkwrap.json @@ -1,95 +1,10 @@ { - "lockfileVersion": 1, + "lockfileVersion": 4, "dependencies": { - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=" - }, - "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=" - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=" - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "is-buffer": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz", - "integrity": "sha1-z8hszV3FpS+oBIkRHGkgxFfi2Ys=" - }, - "kind-of": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.1.0.tgz", - "integrity": "sha1-R11pil5J/15T0U4+cyQp3Iv0z0c=" - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=" - }, - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" - }, "uglify-js": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", - "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=" - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=" + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.1.tgz", + "integrity": "sha512-X5BGTIDH8U6IQ1TIRP62YC36k+ULAa1d59BxlWvPUJ1NkW5L3FwcGfEzuVvGmhJFBu0YJ5Ge25tmRISqCmLiRQ==" } } } diff --git a/packages/spacebars-compiler/.versions b/packages/spacebars-compiler/.versions index d5439ed9b..d90fa1d48 100644 --- a/packages/spacebars-compiler/.versions +++ b/packages/spacebars-compiler/.versions @@ -1,52 +1,55 @@ -allow-deny@1.1.0 -babel-compiler@7.6.1 -babel-runtime@1.5.0 -base64@1.0.12 -binary-heap@1.0.11 -blaze-tools@1.1.2 -boilerplate-generator@1.7.1 -callback-hook@1.3.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.4.0 -ddp-common@1.4.0 -ddp-server@2.3.2 -diff-sequence@1.1.1 -dynamic-import@0.6.0 -ecmascript@0.15.1 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.11.0 -ecmascript-runtime-server@0.10.0 -ejson@1.1.1 -fetch@0.1.1 -geojson-utils@1.0.10 -html-tools@1.1.2 -htmljs@1.1.0 -id-map@1.1.0 -inter-process-messaging@0.1.1 -local-test:spacebars-compiler@1.3.0 -logging@1.2.0 -meteor@1.9.3 -minimongo@1.6.2 -modern-browsers@0.1.5 -modules@0.16.0 -modules-runtime@0.12.0 -mongo@1.11.0 -mongo-decimal@0.1.2 -mongo-dev-server@1.1.0 -mongo-id@1.0.7 -npm-mongo@3.9.0 -ordered-dict@1.1.0 -promise@0.11.2 -random@1.2.0 -react-fast-refresh@0.1.0 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.0 -socket-stream-client@0.3.1 -spacebars-compiler@1.3.0 -tinytest@1.1.0 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.10.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +blaze-tools@2.0.0 +boilerplate-generator@2.0.0 +callback-hook@1.6.0 +check@1.4.2 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.0 +ddp-common@1.4.3 +ddp-server@3.0.0 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.2 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +html-tools@2.0.0 +htmljs@2.0.1 +id-map@1.2.0 +inter-process-messaging@0.1.2 +local-test:spacebars-compiler@2.0.0 +logging@1.3.5 +meteor@2.0.0 +minimongo@2.0.0 +modern-browsers@0.1.11 +modules@0.20.1 +modules-runtime@0.13.2 +mongo@2.0.0 +mongo-decimal@0.1.4-beta300.7 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.3 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +socket-stream-client@0.5.3 +spacebars-compiler@2.0.0 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.0 +webapp-hashing@1.1.2 diff --git a/packages/spacebars-compiler/compiler.js b/packages/spacebars-compiler/compiler.js index 40adcfa25..61d25d8d2 100644 --- a/packages/spacebars-compiler/compiler.js +++ b/packages/spacebars-compiler/compiler.js @@ -115,7 +115,6 @@ export function beautify (code) { } var result = UglifyJSMinify(code, { - fromString: true, mangle: false, compress: false, output: { diff --git a/packages/spacebars-compiler/package.js b/packages/spacebars-compiler/package.js index 1b88b0bb2..5873fe370 100644 --- a/packages/spacebars-compiler/package.js +++ b/packages/spacebars-compiler/package.js @@ -1,20 +1,20 @@ Package.describe({ name: 'spacebars-compiler', summary: "Compiler for Spacebars template language", - version: '1.3.1', + version: '2.0.0', git: 'https://github.com/meteor/blaze.git' }); Npm.depends({ - 'uglify-js': '2.7.5' + 'uglify-js': '3.16.1' }); Package.onUse(function (api) { - api.use('ecmascript@0.15.1'); + api.use('ecmascript@0.16.9'); - api.use('htmljs@1.1.1'); - api.use('html-tools@1.1.3'); - api.use('blaze-tools@1.1.3'); + api.use('htmljs@2.0.1'); + api.use('html-tools@2.0.0'); + api.use('blaze-tools@2.0.0'); api.export('SpacebarsCompiler'); diff --git a/packages/spacebars-tests/.versions b/packages/spacebars-tests/.versions index 1c361fc88..1d782853c 100644 --- a/packages/spacebars-tests/.versions +++ b/packages/spacebars-tests/.versions @@ -1,69 +1,72 @@ -allow-deny@1.1.1 -babel-compiler@7.9.0 -babel-runtime@1.5.1 -base64@1.0.12 -binary-heap@1.0.11 -blaze@2.5.0 -blaze-tools@1.1.0 -boilerplate-generator@1.7.1 -caching-compiler@1.2.2 -caching-html-compiler@1.2.0 -callback-hook@1.4.0 -check@1.3.1 -ddp@1.4.0 -ddp-client@2.5.0 -ddp-common@1.4.0 -ddp-server@2.5.0 -diff-sequence@1.1.1 -dynamic-import@0.7.2 -ecmascript@0.16.2 -ecmascript-runtime@0.8.0 -ecmascript-runtime-client@0.12.1 -ecmascript-runtime-server@0.11.0 -ejson@1.1.2 -es5-shim@4.8.0 -fetch@0.1.1 -geojson-utils@1.0.10 -html-tools@1.1.0 -htmljs@1.1.0 -id-map@1.1.1 -inter-process-messaging@0.1.1 -jquery@1.11.10 -local-test:spacebars-tests@1.3.1 -logging@1.3.1 -markdown@1.0.14 -meteor@1.10.0 -minimongo@1.8.0 -modern-browsers@0.1.8 -modules@0.18.0 -modules-runtime@0.13.0 -mongo@1.15.0 -mongo-decimal@0.1.3 -mongo-dev-server@1.1.0 -mongo-id@1.0.8 -npm-mongo@4.3.1 -observe-sequence@1.0.16 -ordered-dict@1.1.0 -promise@0.12.0 -random@1.2.0 -react-fast-refresh@0.2.3 -reactive-dict@1.3.0 -reactive-var@1.0.11 -reload@1.3.1 -retry@1.1.0 -routepolicy@1.1.1 -session@1.2.0 -socket-stream-client@0.5.0 -spacebars@1.2.0 -spacebars-compiler@1.2.0 -spacebars-tests@1.3.1 -templating@1.4.1 -templating-compiler@1.4.1 -templating-runtime@1.5.0 -templating-tools@1.2.0 -test-helpers@1.3.0 -tinytest@1.2.1 -tracker@1.2.0 -underscore@1.0.10 -webapp@1.13.1 -webapp-hashing@1.1.0 +allow-deny@2.0.0 +babel-compiler@7.11.1 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +blaze@3.0.0 +blaze-tools@2.0.0 +boilerplate-generator@2.0.0 +caching-compiler@2.0.1 +caching-html-compiler@2.0.0 +callback-hook@1.6.0 +check@1.4.4 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.0.2 +ddp-common@1.4.4 +ddp-server@3.0.2 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.16.9 +ecmascript-runtime@0.8.3 +ecmascript-runtime-client@0.12.2 +ecmascript-runtime-server@0.11.1 +ejson@1.1.4 +es5-shim@4.8.1 +facts-base@1.0.2 +fetch@0.1.5 +geojson-utils@1.0.12 +html-tools@2.0.0 +htmljs@2.0.1 +id-map@1.2.0 +inter-process-messaging@0.1.2 +jquery@3.0.2 +local-test:spacebars-tests@2.0.1 +logging@1.3.5 +markdown@2.0.0 +meteor@2.0.1 +minimongo@2.0.1 +modern-browsers@0.1.11 +modules@0.20.2 +modules-runtime@0.13.2 +mongo@2.0.2 +mongo-decimal@0.1.5 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@4.17.4 +observe-sequence@2.0.0 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.2.9 +reactive-dict@1.3.2 +reactive-var@1.0.13 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +session@1.2.2 +socket-stream-client@0.5.3 +spacebars@2.0.0 +spacebars-compiler@2.0.0 +spacebars-tests@2.0.1 +templating@1.4.4 +templating-compiler@2.0.0 +templating-runtime@2.0.0 +templating-tools@2.0.0 +test-helpers@2.0.1 +tinytest@1.3.0 +tracker@1.3.4 +typescript@5.4.3 +underscore@1.6.4 +webapp@2.0.3 +webapp-hashing@1.1.2 diff --git a/packages/spacebars-tests/async_tests.html b/packages/spacebars-tests/async_tests.html new file mode 100644 index 000000000..299a790b3 --- /dev/null +++ b/packages/spacebars-tests/async_tests.html @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/spacebars-tests/async_tests.js b/packages/spacebars-tests/async_tests.js new file mode 100644 index 000000000..82e20f58d --- /dev/null +++ b/packages/spacebars-tests/async_tests.js @@ -0,0 +1,166 @@ +function asyncTest(templateName, testName, fn) { + const name = [templateName, testName].filter(Boolean).join(' '); + Tinytest.addAsync(`spacebars-tests - async - ${name}`, test => { + const template = Blaze.Template[`spacebars_async_tests_${templateName}`]; + const templateCopy = new Blaze.Template(template.viewName, template.renderFunction); + return fn(test, templateCopy, () => { + const div = renderToDiv(templateCopy); + return () => canonicalizeHtml(div.innerHTML); + }); + }); +} + +function asyncSuite(templateName, cases) { + for (const [testName, helpers, before, after, cycles = 1] of cases) { + asyncTest(templateName, testName, async (test, template, render) => { + template.helpers(helpers); + const readHTML = render(); + // Some test cases require more cycles to propagate. + for (let cycle = 0; cycle < cycles; ++cycle) { + test.equal(readHTML(), before); + await new Promise(Tracker.afterFlush); + } + test.equal(readHTML(), after); + }); + } +} + +const getter = v => async () => v; +const thenable = v => ({ then: resolve => Promise.resolve().then(() => resolve(v)) }); +const value = v => Promise.resolve(v); + +asyncSuite('access', [ + ['getter', { x: { y: getter('foo') } }, '', 'foo'], + ['thenable', { x: { y: thenable('foo') } }, '', 'foo'], + ['value', { x: { y: value('foo') } }, '', 'foo'], +]); + +asyncSuite('direct', [ + ['getter', { x: getter('foo') }, '', 'foo'], + ['thenable', { x: thenable('foo') }, '', 'foo'], + ['value', { x: value('foo') }, '', 'foo'], +]); + +asyncTest('missing1', 'outer', async (test, template, render) => { + Blaze._throwNextException = true; + test.throws(render, 'Binding for "b" was not found.'); +}); + +asyncTest('missing2', 'inner', async (test, template, render) => { + Blaze._throwNextException = true; + test.throws(render, 'Binding for "b" was not found.'); +}); + +asyncSuite('attribute', [ + ['getter', { x: getter('foo') }, '', ''], + ['thenable', { x: thenable('foo') }, '', ''], + ['value', { x: value('foo') }, '', ''], +]); + +asyncSuite('attributes', [ + ['getter in getter', { x: getter({ class: getter('foo') }) }, '', ''], // Nested getters are NOT evaluated. + ['getter in thenable', { x: thenable({ class: getter('foo') }) }, '', ''], // Nested getters are NOT evaluated. + ['getter in value', { x: value({ class: getter('foo') }) }, '', ''], // Nested getters are NOT evaluated. + ['static in getter', { x: getter({ class: 'foo' }) }, '', ''], + ['static in thenable', { x: thenable({ class: 'foo' }) }, '', ''], + ['static in value', { x: value({ class: 'foo' }) }, '', ''], + ['thenable in getter', { x: getter({ class: thenable('foo') }) }, '', ''], + ['thenable in thenable', { x: thenable({ class: thenable('foo') }) }, '', ''], + ['thenable in value', { x: value({ class: thenable('foo') }) }, '', ''], + ['value in getter', { x: getter({ class: value('foo') }) }, '', ''], + ['value in thenable', { x: thenable({ class: value('foo') }) }, '', ''], + ['value in value', { x: value({ class: value('foo') }) }, '', ''], +]); + +asyncSuite('attributes_double', [ + ['null lhs getter', { x: getter({ class: null }), y: getter({ class: 'foo' }) }, '', ''], + ['null lhs thenable', { x: thenable({ class: null }), y: thenable({ class: 'foo' }) }, '', ''], + ['null lhs value', { x: value({ class: null }), y: value({ class: 'foo' }) }, '', ''], + ['null rhs getter', { x: getter({ class: 'foo' }), y: getter({ class: null }) }, '', ''], + ['null rhs thenable', { x: thenable({ class: 'foo' }), y: thenable({ class: null }) }, '', ''], + ['null rhs value', { x: value({ class: 'foo' }), y: value({ class: null }) }, '', ''], + ['override getter', { x: getter({ class: 'foo' }), y: getter({ class: 'bar' }) }, '', ''], + ['override thenable', { x: thenable({ class: 'foo' }), y: thenable({ class: 'bar' }) }, '', ''], + ['override value', { x: value({ class: 'foo' }), y: value({ class: 'bar' }) }, '', ''], +]); + +asyncSuite('value_direct', [ + ['getter', { x: getter('foo') }, '', 'foo'], + ['thenable', { x: thenable('foo') }, '', 'foo'], + ['value', { x: value('foo') }, '', 'foo'], +]); + +asyncSuite('value_raw', [ + ['getter', { x: getter('foo') }, '', 'foo'], + ['thenable', { x: thenable('foo') }, '', 'foo'], + ['value', { x: value('foo') }, '', 'foo'], +]); + +asyncSuite('if', [ + ['false', { x: Promise.resolve(false) }, '', '2'], + ['true', { x: Promise.resolve(true) }, '', '1 1'], +]); + +asyncSuite('unless', [ + ['false', { x: Promise.resolve(false) }, '', '1 1'], + ['true', { x: Promise.resolve(true) }, '', '2'], +]); + +asyncSuite('each_old', [ + ['null', { x: Promise.resolve(null) }, '0', '0'], + ['empty', { x: Promise.resolve([]) }, '0', '0'], + ['one', { x: Promise.resolve([1]) }, '0', '1'], + ['two', { x: Promise.resolve([1, 2]) }, '0', '12'], +]); + +asyncSuite('each_new', [ + ['null', { x: Promise.resolve(null) }, '0', '0'], + ['empty', { x: Promise.resolve([]) }, '0', '0'], + ['one', { x: Promise.resolve([1]) }, '0', '1'], + ['two', { x: Promise.resolve([1, 2]) }, '0', '12'], +]); + +asyncSuite('with', [ + ['null', { x: Promise.resolve(null) }, '', '', 2], + ['empty', { x: Promise.resolve({}) }, '', '', 2], + ['direct', { x: Promise.resolve({y: 1}) }, '', '1', 2], + ['wrapped', { x: Promise.resolve({y: Promise.resolve(1)}) }, '', '1', 3], +]); + +// In the following tests pending=1, rejected=2, resolved=3. +const pending = new Promise(() => {}); +const rejected = Promise.reject(); +const resolved = Promise.resolve(); + +// Ignore unhandled rejection error. +rejected.catch(() => {}); + +asyncSuite('state1', [ + ['pending', { x: pending }, '1 a1', '1 a1'], + ['rejected', { x: rejected }, '1 a1', '2 a2'], + ['resolved', { x: resolved }, '1 a1', '3 a3'], +]); + +asyncSuite('state2flat', [ + ['pending pending', { x: pending, y: pending }, '1 a1 b1 ab1', '1 a1 b1 ab1'], + ['pending rejected', { x: pending, y: rejected }, '1 a1 b1 ab1', '1 2 a1 b2 ab1 ab2'], + ['pending resolved', { x: pending, y: resolved }, '1 a1 b1 ab1', '1 3 a1 b3 ab1 ab3'], + ['rejected pending', { x: rejected, y: pending }, '1 a1 b1 ab1', '1 2 a2 b1 ab1 ab2'], + ['rejected rejected', { x: rejected, y: rejected }, '1 a1 b1 ab1', '2 a2 b2 ab2'], + ['rejected resolved', { x: rejected, y: resolved }, '1 a1 b1 ab1', '2 3 a2 b3 ab2 ab3'], + ['resolved pending', { x: resolved, y: pending }, '1 a1 b1 ab1', '1 3 a3 b1 ab1 ab3'], + ['resolved rejected', { x: resolved, y: rejected }, '1 a1 b1 ab1', '2 3 a3 b2 ab2 ab3'], + ['resolved resolved', { x: resolved, y: resolved }, '1 a1 b1 ab1', '3 a3 b3 ab3'], +]); + +asyncSuite('state2nested', [ + ['pending pending', { x: pending, y: pending }, '1 a1 b1 ab1', '1 a1 b1 ab1'], + ['pending rejected', { x: pending, y: rejected }, '1 a1 b1 ab1', '2 a1 b2 ab1 ab2'], + ['pending resolved', { x: pending, y: resolved }, '1 a1 b1 ab1', '3 a1 b3 ab1 ab3'], + ['rejected pending', { x: rejected, y: pending }, '1 a1 b1 ab1', '1 a2 b1 ab1 ab2'], + ['rejected rejected', { x: rejected, y: rejected }, '1 a1 b1 ab1', '2 a2 b2 ab2'], + ['rejected resolved', { x: rejected, y: resolved }, '1 a1 b1 ab1', '3 a2 b3 ab2 ab3'], + ['resolved pending', { x: resolved, y: pending }, '1 a1 b1 ab1', '1 a3 b1 ab1 ab3'], + ['resolved rejected', { x: resolved, y: rejected }, '1 a1 b1 ab1', '2 a3 b2 ab2 ab3'], + ['resolved resolved', { x: resolved, y: resolved }, '1 a1 b1 ab1', '3 a3 b3 ab3'], +]); diff --git a/packages/spacebars-tests/old_templates_tests.js b/packages/spacebars-tests/old_templates_tests.js index aa0afc8ec..e4e22bec4 100644 --- a/packages/spacebars-tests/old_templates_tests.js +++ b/packages/spacebars-tests/old_templates_tests.js @@ -577,7 +577,7 @@ Tinytest.add( Tinytest.add( 'spacebars-tests - old - template_tests - each on cursor', - function (test) { + async function (test) { var tmpl = Template.old_spacebars_template_test_each; var coll = new Mongo.Collection(null); tmpl.items = function () { @@ -590,15 +590,15 @@ Tinytest.add( }; rendersTo('else-clause'); - coll.insert({ text: 'one', pos: 1 }); + await coll.insertAsync({ text: 'one', pos: 1 }); rendersTo('one'); - coll.insert({ text: 'two', pos: 2 }); + await coll.insertAsync({ text: 'two', pos: 2 }); rendersTo('one two'); - coll.update({ text: 'two' }, { $set: { text: 'three' } }); + await coll.updateAsync({ text: 'two' }, { $set: { text: 'three' } }); rendersTo('one three'); - coll.update({ text: 'three' }, { $set: { pos: 0 } }); + await coll.updateAsync({ text: 'three' }, { $set: { pos: 0 } }); rendersTo('three one'); - coll.remove({}); + await coll.removeAsync({}); rendersTo('else-clause'); } ); @@ -682,7 +682,7 @@ Tinytest.add('spacebars-tests - old - template_tests - ..', function (test) { Tinytest.add( 'spacebars-tests - old - template_tests - select tags', - function (test) { + async function (test) { var tmpl = Template.old_spacebars_template_test_select_tag; // {label: (string)} @@ -717,8 +717,8 @@ Tinytest.add( test.equal(divContent(), ['']); - var optgroup1 = optgroups.insert({ label: 'one' }); - var optgroup2 = optgroups.insert({ label: 'two' }); + var optgroup1 = await optgroups.insertAsync({ label: 'one' }); + var optgroup2 = await optgroups.insertAsync({ label: 'two' }); test.equal(divContent(), [ '', ]); - options.insert({ + await options.insertAsync({ optgroup: optgroup1, value: 'value1', selected: false, label: 'label1', }); - options.insert({ + await options.insertAsync({ optgroup: optgroup1, value: 'value2', selected: true, @@ -755,8 +755,8 @@ Tinytest.add( test.equal($(selectEl).find('option')[1].selected, true); // swap selection - options.update({ value: 'value1' }, { $set: { selected: true } }); - options.update({ value: 'value2' }, { $set: { selected: false } }); + await options.updateAsync({ value: 'value1' }, { $set: { selected: true } }); + await options.updateAsync({ value: 'value2' }, { $set: { selected: false } }); Tracker.flush(); test.equal(divContent(), [ @@ -774,8 +774,8 @@ Tinytest.add( test.equal($(selectEl).find('option')[1].selected, false); // change value and label - options.update({ value: 'value1' }, { $set: { value: 'value1.0' } }); - options.update({ value: 'value2' }, { $set: { label: 'label2.0' } }); + await options.updateAsync({ value: 'value1' }, { $set: { value: 'value1.0' } }); + await options.updateAsync({ value: 'value2' }, { $set: { label: 'label2.0' } }); Tracker.flush(); test.equal(divContent(), [ @@ -795,17 +795,17 @@ Tinytest.add( // unselect and then select both options. normally, the second is // selected (since it got selected later). then switch to ', '']); - var optgroup1 = optgroups.insert({ label: 'one' }); - var optgroup2 = optgroups.insert({ label: 'two' }); + var optgroup1 = await optgroups.insertAsync({ label: 'one' }); + var optgroup2 = await optgroups.insertAsync({ label: 'two' }); test.equal(divContent(), [ '', ]); - options.insert({ + await options.insertAsync({ optgroup: optgroup1, value: 'value1', selected: false, label: 'label1', }); - options.insert({ + await options.insertAsync({ optgroup: optgroup1, value: 'value2', selected: true, @@ -870,8 +870,8 @@ Tinytest.add('spacebars-tests - template_tests - select tags', function (test) { test.equal($(selectEl).find('option')[1].selected, true); // swap selection - options.update({ value: 'value1' }, { $set: { selected: true } }); - options.update({ value: 'value2' }, { $set: { selected: false } }); + await options.updateAsync({ value: 'value2' }, { $set: { selected: false } }); + await options.updateAsync({ value: 'value1' }, { $set: { selected: true } }); Tracker.flush(); test.equal(divContent(), [ @@ -889,8 +889,8 @@ Tinytest.add('spacebars-tests - template_tests - select tags', function (test) { test.equal($(selectEl).find('option')[1].selected, false); // change value and label - options.update({ value: 'value1' }, { $set: { value: 'value1.0' } }); - options.update({ value: 'value2' }, { $set: { label: 'label2.0' } }); + await options.updateAsync({ value: 'value1' }, { $set: { value: 'value1.0' } }); + await options.updateAsync({ value: 'value2' }, { $set: { label: 'label2.0' } }); Tracker.flush(); test.equal(divContent(), [ @@ -910,17 +910,17 @@ Tinytest.add('spacebars-tests - template_tests - select tags', function (test) { // unselect and then select both options. normally, the second is // selected (since it got selected later). then switch to