diff --git a/.editorconfig b/.editorconfig
index f2bc96b02..b9d48d491 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,3 +5,7 @@ indent_size = 2
 insert_final_newline = true
 trim_trailing_whitespace = true
 
+[*.yml]
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintignore b/.eslintignore
index 424ad0b0e..46c3a2d3a 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,7 +4,6 @@
 *.sublime-project
 *.sublime-workspace
 npm-debug.log
-sauce_connect.log*
 .idea
 yarn-error.log
 node_modules
@@ -14,7 +13,7 @@ node_modules
 # Generated files
 /coverage/
 /dist/
-/integration-testing/*/dist/
+/tests/integration/*/dist/
 
 # Third-party or files that must remain unchanged
 /spec/expected/
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..c73c050af
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+version: 2
+updates:
+  - package-ecosystem: npm
+    directory: "/"
+    open-pull-requests-limit: 0
+    schedule:
+      interval: weekly
+    allow:
+      - dependency-type: production
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..1c692488d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,89 @@
+name: CI
+
+on:
+  push:
+    branches:
+      - master
+  pull_request: {}
+
+jobs:
+  lint:
+    name: Lint
+    runs-on: 'ubuntu-latest'
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v2
+        with:
+          node-version: '16'
+
+      - name: Install dependencies
+        run: npm ci
+
+      - name: Lint
+        run: npm run lint
+
+  test:
+    name: Test (Node)
+    runs-on: ${{ matrix.operating-system }}
+    strategy:
+      fail-fast: false
+      matrix:
+        operating-system: ['ubuntu-latest', 'windows-latest']
+        # https://nodejs.org/en/about/releases/
+        node-version: ['10', '12', '14', '16', '17']
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          submodules: true
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v2
+        with:
+          node-version: ${{ matrix.node-version }}
+
+      - name: Install dependencies
+        run: npm ci
+
+      - name: Test
+        run: npm run test
+
+      - name: Test (Integration)
+        # https://github.com/webpack/webpack/issues/14532
+        if: ${{ matrix.node-version != '17' }}
+        run: |
+          cd ./tests/integration/rollup-test && ./test.sh && cd -
+          cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
+          cd ./tests/integration/webpack-test && ./test.sh && cd -
+
+  browser:
+    name: Test (Browser)
+    runs-on: 'ubuntu-latest'
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          submodules: true
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v2
+        with:
+          node-version: '16'
+
+      - name: Install dependencies
+        run: npm ci
+
+      - name: Install Playwright
+        run: |
+          npx playwright install-deps
+          npx playwright install
+
+      - name: Build
+        run: npx grunt prepare
+
+      - name: Test
+        run: npm run test:browser
diff --git a/.gitignore b/.gitignore
index 7bccd17e3..0674f3e77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,6 @@
 *.sublime-project
 *.sublime-workspace
 npm-debug.log
-sauce_connect.log*
 .idea
 /yarn-error.log
 /yarn.lock
@@ -15,5 +14,5 @@ node_modules
 # Generated files
 /coverage/
 /dist/
-/integration-testing/*/dist/
+/tests/integration/*/dist/
 /spec/tmp/*
diff --git a/.prettierignore b/.prettierignore
index f160a7f51..7c9924e64 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -14,7 +14,7 @@ node_modules
 # Generated files
 /coverage/
 /dist/
-/integration-testing/*/dist/
+/tests/integration/*/dist/
 
 # Third-party or files that must remain unchanged
 /spec/expected/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e7d347e27..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-language: node_js
-jobs:
-  include:
-  - stage: test
-    name: check javascript (eslint)
-    node_js: lts/*
-    script: npm run lint
-  - stage: test
-    name: check formatting (prettier)
-    node_js: lts/*
-    script: npm run check-format
-  - stage: test
-    name: check typescript definitions (dtslint)
-    node_js: lts/*
-    script: npm run dtslint
-  - stage: test
-    name: extensive tests and publish to aws
-    script: npm run extensive-tests-and-publish-to-aws
-    env:
-    - S3_BUCKET_NAME=builds.handlebarsjs.com
-    - secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
-    - secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
-    - SAUCE_USERNAME=handlebars
-    - secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
-    node_js: '10'
-  - stage: test
-    name: test with latest nodejs-lts
-    node_js: lts/*
-    script: npm run test
-  - stage: test
-    name: test with active nodejs
-    node_js: node
-    script: npm run test
-cache: npm
-email:
-  on_failure: change
-  on_success: never
-git:
-  depth: 100
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0d0afc084..9b6631195 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ Please don't open issues for security issues. Instead, file a report at https://
 
 ## Reporting Issues
 
-Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
+Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
 
 Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]!
 
@@ -14,15 +14,14 @@ In general we are going to ask for an **example** of the problem failing, which
 
 Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
 
-Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
+Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
 
 ## Branches
 
 - The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
 - The branch `master` contains the next version. A release date is not yet specified. Maintainers
   should merge the branch `4.x` into the master branch regularly.
-
-* The branch `3.x` comtains the legacy version `3.x`. Bugfixes are applied seperately (if needed). The branch will not
+- The branch `3.x` contains the legacy version `3.x`. Bugfixes are applied separately (if needed). The branch will not
   be merged with any of the other branches.
 
 ## Pull Requests
@@ -56,7 +55,7 @@ You can also run our set of benchmarks with `grunt bench`.
 The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
 
 If you notice any problems, please report them to the GitHub issue tracker at
-[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
+[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
 
 ## Running Tests
 
@@ -87,14 +86,14 @@ We do linting and formatting in two phases:
 
 - Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
   while warnings are allowed.
-- The travis-ci job also lints all files and checks if they are formatted correctly. In this stage, warnings
+- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
   are forbidden.
 
-You can use the following scripts to make sure that the travis-job does not fail:
+You can use the following scripts to make sure that the CI job does not fail:
 
 - **npm run lint** will run `eslint` and fail on warnings
 - **npm run format** will run `prettier` on all files
-- **npm run check-before-pull-request** will perform all most checks that travis does in its build-job, excluding the "integration-test".
+- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
 - **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
   These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
 
@@ -102,7 +101,7 @@ You can use the following scripts to make sure that the travis-job does not fail
 
 Before attempting the release Handlebars, please make sure that you have the following authorizations:
 
-- Push-access to `wycats/handlebars.js`
+- Push-access to `handlebars-lang/handlebars.js`
 - Publishing rights on npmjs.com for the `handlebars` package
 - Publishing rights on gemfury for the `handlebars-source` package
 - Push-access to the repo for legacy package managers: `components/handlebars`
@@ -110,15 +109,12 @@ Before attempting the release Handlebars, please make sure that you have the fol
 
 _When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
 
-Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
-
 A full release may be completed with the following:
 
 ```
 npm ci
-yo release
+npx grunt
 npm publish
-yo release:publish components handlebars.js dist/components/
 
 cd dist/components/
 gem build handlebars-source.gemspec
@@ -135,13 +131,13 @@ in those places still point to the latest version
 
 When everything is OK, the **handlebars site** needs to be updated.
 
-Go to the master branch of the repo [handlebars-lang/handlebarsjs.com-github-pages](https://github.com/handlebars-lang/handlebarsjs.com-github-pages/tree/master)
+Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
 and make a minimal change to the README. This will invoke a github-action that redeploys
 the site, fetching the latest version-number from the npm-registry.
 (note that the default-branch of this repo is not the master and regular changes are done
 in the `handlebars-lang/docs`-repo).
 
 [generator-release]: https://github.com/walmartlabs/generator-release
-[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
-[issue]: https://github.com/wycats/handlebars.js/issues/new
+[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
+[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
 [jsfiddle]: https://jsfiddle.net/4nbwjaqz/4/
diff --git a/FAQ.md b/FAQ.md
index 35691b10e..4edcb8ba4 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -2,11 +2,11 @@
 
 ## How can I file a bug report:
 
-  See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
+  See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 ## Why isn't my Mustache template working?
 
-  Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
+  Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
 
 ## Why is it slower when compiling?
 
@@ -36,16 +36,18 @@
   ```sh
   handlebars --version
   ```
+
   If using the integrated precompiler and
 
   ```javascript
   console.log(Handlebars.VERSION);
   ```
+
   On the client side.
 
   We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
 
-  Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
+  Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 ## How do I load the runtime library when using AMD?
 
diff --git a/Gruntfile.js b/Gruntfile.js
index 07ff4c242..a347aa8c6 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -3,7 +3,7 @@ module.exports = function(grunt) {
   grunt.initConfig({
     pkg: grunt.file.readJSON('package.json'),
 
-    clean: ['tmp', 'dist', 'integration-testing/**/node_modules'],
+    clean: ['tmp', 'dist', 'tests/integration/**/node_modules'],
 
     copy: {
       dist: {
@@ -110,55 +110,10 @@ module.exports = function(grunt) {
         }
       }
     },
-    'saucelabs-mocha': {
-      all: {
-        options: {
-          build: process.env.TRAVIS_JOB_ID,
-          urls: ['http://localhost:9999/spec/?headless=true'],
-          detailedError: true,
-          concurrency: 4,
-          browsers: [
-            { browserName: 'chrome' },
-            { browserName: 'firefox', platform: 'Linux' }
-            // {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
-            // {browserName: 'safari', version: 8, platform: 'OS X 10.10'},
-            // {
-            //   browserName: 'internet explorer',
-            //   version: 11,
-            //   platform: 'Windows 8.1'
-            // },
-            // {
-            //   browserName: 'internet explorer',
-            //   version: 10,
-            //   platform: 'Windows 8'
-            // }
-          ]
-        }
-      },
-      sanity: {
-        options: {
-          build: process.env.TRAVIS_JOB_ID,
-          urls: [
-            'http://localhost:9999/spec/umd.html?headless=true',
-            'http://localhost:9999/spec/umd-runtime.html?headless=true'
-          ],
-          detailedError: true,
-          concurrency: 2,
-          browsers: [
-            { browserName: 'chrome' },
-            {
-              browserName: 'internet explorer',
-              version: 10,
-              platform: 'Windows 8'
-            }
-          ]
-        }
-      }
-    },
 
     bgShell: {
       integrationTests: {
-        cmd: './integration-testing/run-integration-tests.sh',
+        cmd: './tests/integration/run-integration-tests.sh',
         bg: false,
         fail: true
       }
@@ -185,50 +140,40 @@ module.exports = function(grunt) {
   grunt.loadNpmTasks('grunt-contrib-watch');
   grunt.loadNpmTasks('grunt-babel');
   grunt.loadNpmTasks('grunt-bg-shell');
-  grunt.loadNpmTasks('@knappi/grunt-saucelabs');
   grunt.loadNpmTasks('grunt-webpack');
 
   grunt.task.loadTasks('tasks');
 
-  this.registerTask(
-    'build',
-    'Builds a distributable version of the current project',
-    ['node', 'globals']
-  );
-
-  this.registerTask('node', ['babel:cjs']);
-  this.registerTask('globals', ['webpack']);
-
-  this.registerTask('release', 'Build final packages', [
+  grunt.registerTask('node', ['babel:cjs']);
+  grunt.registerTask('globals', ['webpack']);
+  grunt.registerTask('release', 'Build final packages', [
     'uglify',
     'test:min',
     'copy:dist',
     'copy:components'
   ]);
 
-  this.registerTask('test', ['test:bin', 'test:cov']);
-
-  grunt.registerTask('bench', ['metrics']);
-
-  if (process.env.SAUCE_ACCESS_KEY) {
-    grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
-  } else {
-    grunt.registerTask('sauce', []);
-  }
-
-  // Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
+  // Requires secret properties from .travis.yaml
   grunt.registerTask('extensive-tests-and-publish-to-aws', [
     'default',
     'bgShell:integrationTests',
-    'sauce',
     'metrics',
     'publish-to-aws'
   ]);
+
   grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
 
   // === Primary tasks ===
   grunt.registerTask('dev', ['clean', 'connect', 'watch']);
   grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
+  grunt.registerTask('test', ['test:bin', 'test:cov']);
+  grunt.registerTask('bench', ['metrics']);
+  grunt.registerTask('prepare', ['build', 'concat:tests']);
+  grunt.registerTask(
+    'build',
+    'Builds a distributable version of the current project',
+    ['node', 'globals']
+  );
   grunt.registerTask('integration-tests', [
     'default',
     'bgShell:integrationTests'
diff --git a/README.markdown b/README.markdown
index ce54528c2..a094d4c5a 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,7 +1,7 @@
-[![Travis Build Status](https://img.shields.io/travis/handlebars-lang/handlebars.js/master.svg)](https://travis-ci.org/github/handlebars-lang/handlebars.js)
-[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/wycats/handlebars.js?branch=master&svg=true)](https://ci.appveyor.com/project/wycats/handlebars-js)
-[![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](https://saucelabs.com/u/handlebars)
+[![CI Build Status](https://img.shields.io/github/workflow/status/handlebars-lang/handlebars.js/ci/master)](https://github.com/handlebars-lang/handlebars.js/actions)
 [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/handlebars/badge?style=rounded)](https://www.jsdelivr.com/package/npm/handlebars)
+[![Bundle size](https://img.shields.io/bundlephobia/minzip/handlebars?label=minified%20%2B%20gzipped)](https://bundlephobia.com/package/handlebars)
+[![Install size](https://packagephobia.com/badge?p=handlebars)](https://packagephobia.com/result?p=handlebars)
 
 Handlebars.js
 =============
@@ -21,7 +21,7 @@ Usage
 -----
 In general, the syntax of Handlebars.js templates is a superset
 of Mustache templates. For basic syntax, check out the [Mustache
-manpage](http://mustache.github.com/mustache.5.html).
+manpage](https://mustache.github.io/mustache.5.html).
 
 Once you have a template, use the `Handlebars.compile` method to compile
 the template into a function. The generated function takes a context
@@ -63,7 +63,7 @@ templates easier and also changes a tiny detail of how partials work.
 - [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
 - [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
 
-Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
+Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
 
 ### Compatibility
 
@@ -89,8 +89,6 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
 Older versions and other runtimes are likely to work but have not been formally
 tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
 
-[![Selenium Test Status](https://saucelabs.com/browser-matrix/handlebars.svg)](https://saucelabs.com/u/handlebars)
-
 Performance
 -----------
 
@@ -102,7 +100,7 @@ does have some big performance advantages. Justin Marney, a.k.a.
 [gotascii](http://github.com/gotascii), confirmed that with an
 [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
 rewritten Handlebars (current version) is faster than the old version,
-with many [performance tests](https://travis-ci.org/handlebars-lang/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
+with many performance tests being 5 to 7 times faster than the Mustache equivalent.
 
 
 Upgrading
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 499e57995..000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-# Test against these versions of Node.js
-environment:
-  matrix:
-    - nodejs_version: "10"
-
-platform:
-  - x64
-
-# Install scripts (runs after repo cloning)
-install:
-  # Get the latest stable version of Node.js
-  - ps: Install-Product node $env:nodejs_version $env:platform
-  # Clone submodules (mustache spec)
-  - cmd: git submodule update --init --recursive
-  # Install modules
-  - cmd: npm ci
-
-
-# Post-install test scripts
-test_script:
-  # Output useful info for debugging
-  - cmd: node --version
-  - cmd: npm --version
-  # Run tests
-  - cmd: npm run test
-
-# Don't actually build
-build: off
-
-on_failure:
-  - cmd: 7z a coverage.zip coverage
-  - cmd: appveyor PushArtifact coverage.zip
-
-
-# Set build version format here instead of in the admin panel
-version: "{build}"
diff --git a/bench/.eslintrc b/bench/.eslintrc
deleted file mode 100644
index 823629cd7..000000000
--- a/bench/.eslintrc
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "extends": "prettier",
-  "globals": {
-    "require": true
-  },
-  "rules": {
-    // Disabling for tests, for now.
-    "no-path-concat": 0,
-
-    "no-var": 0,
-    "no-shadow": 0,
-    "handle-callback-err": 0,
-    "no-console": 0
-  }
-}
diff --git a/components/handlebars-source.gemspec b/components/handlebars-source.gemspec
index 7b5d59566..8c901b5f9 100644
--- a/components/handlebars-source.gemspec
+++ b/components/handlebars-source.gemspec
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
   gem.date          = Time.now.strftime("%Y-%m-%d")
   gem.description   = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
   gem.summary       = %q{Handlebars.js source code wrapper}
-  gem.homepage      = "https://github.com/wycats/handlebars.js/"
+  gem.homepage      = "https://github.com/handlebars-lang/handlebars.js/"
   gem.version       = package["version"].sub "-", "."
   gem.license       = "MIT"
 
diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec
index 43da56977..7e9bc6183 100644
--- a/components/handlebars.js.nuspec
+++ b/components/handlebars.js.nuspec
@@ -4,8 +4,8 @@
 		<id>handlebars.js</id>
 		<version>5.0.0-alpha.1</version>
 		<authors>handlebars.js Authors</authors>
-		<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
-		<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
+		<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
+		<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
 		<requireLicenseAcceptance>false</requireLicenseAcceptance>
 		<description>Extension of the Mustache logicless template language</description>
 		<releaseNotes></releaseNotes>
diff --git a/docs/decorators-api.md b/docs/decorators-api.md
index e4a5df9a3..2b9e8e803 100644
--- a/docs/decorators-api.md
+++ b/docs/decorators-api.md
@@ -18,4 +18,4 @@ Decorators are executed when the block program is instantiated and are passed `(
 
 Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
 
-The [inline partial](https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
+The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
diff --git a/lib/.eslintrc.js b/lib/.eslintrc.js
new file mode 100644
index 000000000..dd1334d41
--- /dev/null
+++ b/lib/.eslintrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+  env: {
+    // Handlebars should run natively in the browser
+    node: false
+  }
+};
diff --git a/lib/handlebars/compiler/code-gen.js b/lib/handlebars/compiler/code-gen.js
index 68180288b..9feb61096 100644
--- a/lib/handlebars/compiler/code-gen.js
+++ b/lib/handlebars/compiler/code-gen.js
@@ -8,7 +8,7 @@ try {
   if (typeof define !== 'function' || !define.amd) {
     // We don't support this in AMD environments. For these environments, we assume that
     // they are running on the browser and thus have no need for the source-map library.
-    let SourceMap = require('source-map');
+    let SourceMap = require('source-map'); // eslint-disable-line no-undef
     SourceNode = SourceMap.SourceNode;
   }
 } catch (err) {
diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js
index 47482f9ec..0c7e6263e 100644
--- a/lib/handlebars/helpers/each.js
+++ b/lib/handlebars/helpers/each.js
@@ -44,9 +44,9 @@ export default function(instance) {
             execIteration(i, i, i === context.length - 1);
           }
         }
-      } else if (global.Symbol && context[global.Symbol.iterator]) {
+      } else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
         const newContext = [];
-        const iterator = context[global.Symbol.iterator]();
+        const iterator = context[Symbol.iterator]();
         for (let it = iterator.next(); !it.done; it = iterator.next()) {
           newContext.push(it.value);
         }
diff --git a/lib/handlebars/internal/proto-access.js b/lib/handlebars/internal/proto-access.js
index f58cab509..1d040c4b3 100644
--- a/lib/handlebars/internal/proto-access.js
+++ b/lib/handlebars/internal/proto-access.js
@@ -1,5 +1,5 @@
 import { createNewLookupObject } from './create-new-lookup-object';
-import * as logger from '../logger';
+import logger from '../logger';
 
 const loggedProperties = Object.create(null);
 
diff --git a/lib/handlebars/no-conflict.js b/lib/handlebars/no-conflict.js
index 618b750f0..5a6db3e9a 100644
--- a/lib/handlebars/no-conflict.js
+++ b/lib/handlebars/no-conflict.js
@@ -1,6 +1,6 @@
 export default function(Handlebars) {
   /* istanbul ignore next */
-  let root = typeof global !== 'undefined' ? global : window,
+  let root = typeof global !== 'undefined' ? global : window, // eslint-disable-line no-undef
     $Handlebars = root.Handlebars;
   /* istanbul ignore next */
   Handlebars.noConflict = function() {
diff --git a/lib/index.js b/lib/index.js
index 06547f1db..be8e69cd2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,5 +1,6 @@
 // USAGE:
 // var handlebars = require('handlebars');
+/* eslint-env node */
 /* eslint-disable no-var */
 
 // var local = handlebars.create();
diff --git a/lib/precompiler.js b/lib/precompiler.js
index 689c9f4d9..ab6056932 100644
--- a/lib/precompiler.js
+++ b/lib/precompiler.js
@@ -1,3 +1,4 @@
+/* eslint-env node */
 /* eslint-disable no-console */
 import Async from 'neo-async';
 import fs from 'fs';
diff --git a/package.json b/package.json
index bbd8fe936..10ba4a397 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "handlebars",
   "version": "5.0.0-alpha.1",
   "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
-  "homepage": "http://www.handlebarsjs.com/",
+  "homepage": "https://www.handlebarsjs.com/",
   "keywords": [
     "handlebars",
     "mustache",
@@ -11,7 +11,7 @@
   ],
   "repository": {
     "type": "git",
-    "url": "https://github.com/wycats/handlebars.js.git"
+    "url": "https://github.com/handlebars-lang/handlebars.js.git"
   },
   "author": "Yehuda Katz",
   "license": "MIT",
@@ -21,7 +21,7 @@
   },
   "dependencies": {
     "@handlebars/parser": "^1.1.0",
-    "neo-async": "^2.6.0",
+    "neo-async": "^2.6.2",
     "source-map": "^0.6.1",
     "yargs": "^15.3.1"
   },
@@ -29,7 +29,7 @@
     "uglify-js": "^3.1.4"
   },
   "devDependencies": {
-    "@knappi/grunt-saucelabs": "^9.0.2",
+    "@playwright/test": "^1.17.1",
     "aws-sdk": "^2.1.49",
     "babel-loader": "^5.0.0",
     "babel-runtime": "^5.1.10",
@@ -43,7 +43,7 @@
     "eco": "~1.1.0-rc-3",
     "eslint": "^6.7.2",
     "eslint-config-prettier": "^6.7.0",
-    "eslint-plugin-compat": "^3.3.0",
+    "eslint-plugin-compat": "^3.13.0",
     "eslint-plugin-es5": "^1.4.1",
     "fs-extra": "^8.1.0",
     "grunt": "^1.0.4",
@@ -84,15 +84,20 @@
     "handlebars": "bin/handlebars"
   },
   "scripts": {
+    "build": "grunt build",
     "format": "prettier --write '**/*.js' && eslint --fix .",
-    "check-format": "prettier --check '**/*.js'",
-    "lint": "eslint --max-warnings 0 .",
-    "dtslint": "dtslint types",
-    "test": "grunt",
-    "extensive-tests-and-publish-to-aws": "npx mocha tasks/task-tests/ && grunt --stack extensive-tests-and-publish-to-aws",
-    "integration-test": "grunt integration-tests",
+    "lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
+    "lint:eslint": "eslint --max-warnings 0 .",
+    "lint:prettier": "prettier --check '**/*.js'",
+    "lint:types": "dtslint types",
+    "test": "npm run test:mocha",
+    "test:mocha": "grunt build && grunt test",
+    "test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
+    "test:integration": "grunt integration-tests",
+    "test:serve": "grunt connect:server:keepalive",
+    "extensive-tests-and-publish-to-aws": "npx mocha tasks/tests/ && grunt --stack extensive-tests-and-publish-to-aws",
     "--- combined tasks ---": "",
-    "check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:dtslint npm:check-format npm:test"
+    "check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
   },
   "jspm": {
     "main": "handlebars",
@@ -109,7 +114,6 @@
     "dist/amd/**/*.js",
     "dist/cjs/**/*.js",
     "lib",
-    "print-script",
     "release-notes.md",
     "runtime.js",
     "types/*.d.ts",
@@ -121,7 +125,7 @@
     }
   },
   "lint-staged": {
-    "*.{js,css,json,md}": [
+    "*.{js,css,json}": [
       "prettier --write",
       "git add"
     ],
diff --git a/spec/index.html b/spec/index.html
index 3f2068ed1..a1fb52291 100644
--- a/spec/index.html
+++ b/spec/index.html
@@ -62,7 +62,7 @@
         }
         var runner = mocha.run();
 
-        //Reporting for saucelabs
+        // Reporting to test-runner
         var failedTests = [];
         runner.on('end', function(){
           window.mochaResults = runner.stats;
diff --git a/spec/umd-runtime.html b/spec/umd-runtime.html
index 49b721ded..afe5928ab 100644
--- a/spec/umd-runtime.html
+++ b/spec/umd-runtime.html
@@ -54,7 +54,7 @@
           }
           var runner = mocha.run();
 
-          //Reporting for saucelabs
+          // Reporting to test-runner
           var failedTests = [];
           runner.on('end', function(){
             window.mochaResults = runner.stats;
diff --git a/spec/umd.html b/spec/umd.html
index ae461ed69..9e6e19d03 100644
--- a/spec/umd.html
+++ b/spec/umd.html
@@ -74,7 +74,7 @@
             }
             var runner = mocha.run();
 
-            //Reporting for saucelabs
+            // Reporting to test-runner
             var failedTests = [];
             runner.on('end', function(){
               window.mochaResults = runner.stats;
diff --git a/tasks/metrics.js b/tasks/metrics.js
index acc3cece4..58aaae286 100644
--- a/tasks/metrics.js
+++ b/tasks/metrics.js
@@ -1,4 +1,4 @@
-const metrics = require('../bench');
+const metrics = require('../tests/bench');
 const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
 
 module.exports = function(grunt) {
diff --git a/tasks/task-tests/README.md b/tasks/task-tests/README.md
deleted file mode 100644
index f4a88806c..000000000
--- a/tasks/task-tests/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Use `mocha tasks/task-tests` to run these tests
diff --git a/tasks/task-tests/.eslintrc.js b/tasks/tests/.eslintrc.js
similarity index 100%
rename from tasks/task-tests/.eslintrc.js
rename to tasks/tests/.eslintrc.js
diff --git a/tasks/tests/README.md b/tasks/tests/README.md
new file mode 100644
index 000000000..3c4051cdf
--- /dev/null
+++ b/tasks/tests/README.md
@@ -0,0 +1 @@
+Use `mocha tasks/tests` to run these tests
diff --git a/tasks/task-tests/git.test.js b/tasks/tests/git.test.js
similarity index 100%
rename from tasks/task-tests/git.test.js
rename to tasks/tests/git.test.js
diff --git a/tasks/task-tests/mocha.opts b/tasks/tests/mocha.opts
similarity index 100%
rename from tasks/task-tests/mocha.opts
rename to tasks/tests/mocha.opts
diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js
new file mode 100644
index 000000000..8f806f344
--- /dev/null
+++ b/tests/.eslintrc.js
@@ -0,0 +1,10 @@
+module.exports = {
+  root: true,
+  extends: ['eslint:recommended', 'prettier'],
+  env: {
+    node: true
+  },
+  parserOptions: {
+    ecmaVersion: 6
+  }
+};
diff --git a/bench/dist-size.js b/tests/bench/dist-size.js
similarity index 100%
rename from bench/dist-size.js
rename to tests/bench/dist-size.js
diff --git a/bench/index.js b/tests/bench/index.js
similarity index 100%
rename from bench/index.js
rename to tests/bench/index.js
diff --git a/bench/precompile-size.js b/tests/bench/precompile-size.js
similarity index 94%
rename from bench/precompile-size.js
rename to tests/bench/precompile-size.js
index 5aca9423c..7e698a389 100644
--- a/bench/precompile-size.js
+++ b/tests/bench/precompile-size.js
@@ -3,7 +3,7 @@ var _ = require('underscore'),
 
 module.exports = function(grunt, callback) {
   // Deferring to here in case we have a build for parser, etc as part of this grunt exec
-  var Handlebars = require('../lib');
+  var Handlebars = require('../../lib');
 
   var templateSizes = {};
   _.each(templates, function(info, template) {
diff --git a/bench/templates/arguments.js b/tests/bench/templates/arguments.js
similarity index 100%
rename from bench/templates/arguments.js
rename to tests/bench/templates/arguments.js
diff --git a/bench/templates/array-each.js b/tests/bench/templates/array-each.js
similarity index 100%
rename from bench/templates/array-each.js
rename to tests/bench/templates/array-each.js
diff --git a/bench/templates/array-mustache.js b/tests/bench/templates/array-mustache.js
similarity index 100%
rename from bench/templates/array-mustache.js
rename to tests/bench/templates/array-mustache.js
diff --git a/bench/templates/complex.dust b/tests/bench/templates/complex.dust
similarity index 100%
rename from bench/templates/complex.dust
rename to tests/bench/templates/complex.dust
diff --git a/bench/templates/complex.eco b/tests/bench/templates/complex.eco
similarity index 100%
rename from bench/templates/complex.eco
rename to tests/bench/templates/complex.eco
diff --git a/bench/templates/complex.handlebars b/tests/bench/templates/complex.handlebars
similarity index 100%
rename from bench/templates/complex.handlebars
rename to tests/bench/templates/complex.handlebars
diff --git a/bench/templates/complex.js b/tests/bench/templates/complex.js
similarity index 100%
rename from bench/templates/complex.js
rename to tests/bench/templates/complex.js
diff --git a/bench/templates/complex.mustache b/tests/bench/templates/complex.mustache
similarity index 100%
rename from bench/templates/complex.mustache
rename to tests/bench/templates/complex.mustache
diff --git a/bench/templates/data.js b/tests/bench/templates/data.js
similarity index 100%
rename from bench/templates/data.js
rename to tests/bench/templates/data.js
diff --git a/bench/templates/depth-1.js b/tests/bench/templates/depth-1.js
similarity index 100%
rename from bench/templates/depth-1.js
rename to tests/bench/templates/depth-1.js
diff --git a/bench/templates/depth-2.js b/tests/bench/templates/depth-2.js
similarity index 100%
rename from bench/templates/depth-2.js
rename to tests/bench/templates/depth-2.js
diff --git a/bench/templates/index.js b/tests/bench/templates/index.js
similarity index 100%
rename from bench/templates/index.js
rename to tests/bench/templates/index.js
diff --git a/bench/templates/object-mustache.js b/tests/bench/templates/object-mustache.js
similarity index 100%
rename from bench/templates/object-mustache.js
rename to tests/bench/templates/object-mustache.js
diff --git a/bench/templates/object.js b/tests/bench/templates/object.js
similarity index 100%
rename from bench/templates/object.js
rename to tests/bench/templates/object.js
diff --git a/bench/templates/partial-recursion.js b/tests/bench/templates/partial-recursion.js
similarity index 100%
rename from bench/templates/partial-recursion.js
rename to tests/bench/templates/partial-recursion.js
diff --git a/bench/templates/partial.js b/tests/bench/templates/partial.js
similarity index 100%
rename from bench/templates/partial.js
rename to tests/bench/templates/partial.js
diff --git a/bench/templates/paths.js b/tests/bench/templates/paths.js
similarity index 100%
rename from bench/templates/paths.js
rename to tests/bench/templates/paths.js
diff --git a/bench/templates/string.js b/tests/bench/templates/string.js
similarity index 100%
rename from bench/templates/string.js
rename to tests/bench/templates/string.js
diff --git a/bench/templates/subexpression.js b/tests/bench/templates/subexpression.js
similarity index 100%
rename from bench/templates/subexpression.js
rename to tests/bench/templates/subexpression.js
diff --git a/bench/templates/variables.js b/tests/bench/templates/variables.js
similarity index 100%
rename from bench/templates/variables.js
rename to tests/bench/templates/variables.js
diff --git a/bench/throughput.js b/tests/bench/throughput.js
similarity index 98%
rename from bench/throughput.js
rename to tests/bench/throughput.js
index b5e3a56a5..cd0f88b0f 100644
--- a/bench/throughput.js
+++ b/tests/bench/throughput.js
@@ -145,7 +145,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
 
 module.exports = function(grunt, callback) {
   // Deferring load incase we are being run inline with the grunt build
-  Handlebars = require('../lib');
+  Handlebars = require('../../lib');
 
   console.log('Execution Throughput');
   runner(grunt, makeSuite, function(times, scaled) {
diff --git a/bench/util/benchwarmer.js b/tests/bench/util/benchwarmer.js
similarity index 90%
rename from bench/util/benchwarmer.js
rename to tests/bench/util/benchwarmer.js
index 90415bf56..a9ad6413d 100644
--- a/bench/util/benchwarmer.js
+++ b/tests/bench/util/benchwarmer.js
@@ -11,8 +11,6 @@ function BenchWarmer() {
   this.errors = {};
 }
 
-var print = require('util').print;
-
 BenchWarmer.prototype = {
   winners: function(benches) {
     return Benchmark.filter(benches, 'fastest');
@@ -69,7 +67,7 @@ BenchWarmer.prototype = {
 
         self.startLine('');
 
-        print('\n');
+        console.log('\n');
         self.printHeader('scaled');
         _.each(self.scaled, function(value, name) {
           self.startLine(name);
@@ -78,7 +76,7 @@ BenchWarmer.prototype = {
             self.writeValue(value[lang] || '');
           });
         });
-        print('\n');
+        console.log('\n');
 
         var errors = false,
           prop,
@@ -94,16 +92,16 @@ BenchWarmer.prototype = {
         }
 
         if (errors) {
-          print('\n\nErrors:\n');
+          console.log('\n\nErrors:\n');
           Object.keys(self.errors).forEach(function(prop) {
             if (self.errors[prop].error.message !== 'EWOT') {
               bench = self.errors[prop];
-              print('\n' + bench.name + ':\n');
-              print(bench.error.message);
+              console.log('\n' + bench.name + ':\n');
+              console.log(bench.error.message);
               if (bench.error.stack) {
-                print(bench.error.stack.join('\n'));
+                console.log(bench.error.stack.join('\n'));
               }
-              print('\n');
+              console.log('\n');
             }
           });
         }
@@ -112,7 +110,7 @@ BenchWarmer.prototype = {
       }
     });
 
-    print('\n');
+    console.log('\n');
   },
 
   scaleTimes: function() {
@@ -163,11 +161,11 @@ BenchWarmer.prototype = {
     }
 
     if (winners) {
-      print('WINNER(S)');
+      console.log('WINNER(S)');
       horSize = horSize + 'WINNER(S)'.length;
     }
 
-    print('\n' + new Array(horSize + 1).join('-'));
+    console.log('\n' + new Array(horSize + 1).join('-'));
   },
 
   startLine: function(name) {
@@ -179,8 +177,8 @@ BenchWarmer.prototype = {
 
     this.currentBenches = [];
 
-    print(winners.join(', '));
-    print('\n');
+    console.log(winners.join(', '));
+    console.log('\n');
 
     if (name) {
       this.writeValue(name);
@@ -216,7 +214,7 @@ BenchWarmer.prototype = {
   writeValue: function(out) {
     var padding = this.benchSize - out.length + 1;
     out = out + new Array(padding).join(' ');
-    print(out);
+    console.log(out);
   }
 };
 
diff --git a/bench/util/template-runner.js b/tests/bench/util/template-runner.js
similarity index 100%
rename from bench/util/template-runner.js
rename to tests/bench/util/template-runner.js
diff --git a/tests/browser/.eslintrc.js b/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..344096aeb
--- /dev/null
+++ b/tests/browser/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+  parserOptions: {
+    ecmaVersion: 2020
+  }
+};
diff --git a/tests/browser/README.md b/tests/browser/README.md
new file mode 100644
index 000000000..b35a5a2b6
--- /dev/null
+++ b/tests/browser/README.md
@@ -0,0 +1,14 @@
+# Browser Tests with Playwright
+
+These tests execute Mocha tests from the `spec`-folder in multiple browsers.
+
+## Using Docker
+
+Execute the following commands in the project root:
+
+```bash
+npm install
+npx grunt prepare
+docker pull mcr.microsoft.com/playwright:focal
+docker run -it --rm --volume $(pwd):/srv/app --workdir /srv/app --ipc=host mcr.microsoft.com/playwright:focal npm run test:browser
+```
\ No newline at end of file
diff --git a/tests/browser/playwright.config.js b/tests/browser/playwright.config.js
new file mode 100644
index 000000000..84731b97e
--- /dev/null
+++ b/tests/browser/playwright.config.js
@@ -0,0 +1,27 @@
+const { devices } = require('@playwright/test');
+
+/** @type {import('@playwright/test').PlaywrightTestConfig} */
+const config = {
+  projects: [
+    {
+      name: 'chromium',
+      use: { ...devices['Desktop Chrome'] }
+    },
+    {
+      name: 'firefox',
+      use: { ...devices['Desktop Firefox'] }
+    },
+    {
+      name: 'webkit',
+      use: { ...devices['Desktop Safari'] }
+    }
+  ],
+  reporter: 'list',
+  webServer: {
+    command: 'npm run test:serve',
+    port: 9999,
+    reuseExistingServer: false
+  }
+};
+
+module.exports = config;
diff --git a/tests/browser/spec.js b/tests/browser/spec.js
new file mode 100644
index 000000000..7afe76db6
--- /dev/null
+++ b/tests/browser/spec.js
@@ -0,0 +1,33 @@
+const { test, expect } = require('@playwright/test');
+
+async function waitForMochaAndAssertResult(page) {
+  await page.waitForFunction(() => window.mochaResults); // eslint-disable-line no-undef
+  const mochaResults = await page.evaluate('window.mochaResults');
+
+  expect(mochaResults.failures).toBe(0);
+}
+
+test('Spec handlebars.js', async ({ page, baseURL }) => {
+  await page.goto(`${baseURL}/spec/?headless=true`);
+  await waitForMochaAndAssertResult(page);
+});
+
+test('Spec handlebars.amd.js (AMD)', async ({ page, baseURL }) => {
+  await page.goto(`${baseURL}/spec/amd.html?headless=true`);
+  await waitForMochaAndAssertResult(page);
+});
+
+test('Spec handlebars.runtime.amd.js (AMD)', async ({ page, baseURL }) => {
+  await page.goto(`${baseURL}/spec/amd-runtime.html?headless=true`);
+  await waitForMochaAndAssertResult(page);
+});
+
+test('Spec handlebars.js (UMD)', async ({ page, baseURL }) => {
+  await page.goto(`${baseURL}/spec/umd.html?headless=true`);
+  await waitForMochaAndAssertResult(page);
+});
+
+test('Spec handlebars.runtime.js (UMD)', async ({ page, baseURL }) => {
+  await page.goto(`${baseURL}/spec/umd-runtime.html?headless=true`);
+  await waitForMochaAndAssertResult(page);
+});
diff --git a/integration-testing/README.md b/tests/integration/README.md
similarity index 55%
rename from integration-testing/README.md
rename to tests/integration/README.md
index bd9554c53..7dcde29e8 100644
--- a/integration-testing/README.md
+++ b/tests/integration/README.md
@@ -1,12 +1,10 @@
 Add a new integration test by creating a new subfolder
 
-Add a file "test.sh" to that runs the test. "test.sh" should exit with a non-zero exit code 
+Add a file "test.sh" to that runs the test. "test.sh" should exit with a non-zero exit code
 and display an error message, if something goes wrong.
 
-* An integration test should reflect real-world setups that use handlebars.
-* It should compile a minimal template and compare the output to an expected output.
-* It should use "../.." as dependency for Handlebars so that the currently built library is used.
+- An integration test should reflect real-world setups that use handlebars.
+- It should compile a minimal template and compare the output to an expected output.
+- It should use "../.." as dependency for Handlebars so that the currently built library is used.
 
-Currently, integration tests are only running on Linux, especially in travis-ci.
-
-  
\ No newline at end of file
+Currently, integration tests are only running on Linux, especially in our CI GitHub action.
diff --git a/integration-testing/multi-nodejs-test/.eslintrc.js b/tests/integration/multi-nodejs-test/.eslintrc.js
similarity index 100%
rename from integration-testing/multi-nodejs-test/.eslintrc.js
rename to tests/integration/multi-nodejs-test/.eslintrc.js
diff --git a/integration-testing/multi-nodejs-test/.gitignore b/tests/integration/multi-nodejs-test/.gitignore
similarity index 100%
rename from integration-testing/multi-nodejs-test/.gitignore
rename to tests/integration/multi-nodejs-test/.gitignore
diff --git a/integration-testing/multi-nodejs-test/package.json b/tests/integration/multi-nodejs-test/package.json
similarity index 91%
rename from integration-testing/multi-nodejs-test/package.json
rename to tests/integration/multi-nodejs-test/package.json
index 2fac51d2d..6002c0ce2 100644
--- a/integration-testing/multi-nodejs-test/package.json
+++ b/tests/integration/multi-nodejs-test/package.json
@@ -7,7 +7,7 @@
   "private": true,
   "license": "MIT",
   "dependencies": {
-    "handlebars": "file:../.."
+    "handlebars": "file:../../.."
   },
   "scripts": {
     "test": "node run-handlebars.js",
diff --git a/integration-testing/multi-nodejs-test/precompile-test-template.txt.hbs b/tests/integration/multi-nodejs-test/precompile-test-template.txt.hbs
similarity index 100%
rename from integration-testing/multi-nodejs-test/precompile-test-template.txt.hbs
rename to tests/integration/multi-nodejs-test/precompile-test-template.txt.hbs
diff --git a/integration-testing/multi-nodejs-test/run-handlebars.js b/tests/integration/multi-nodejs-test/run-handlebars.js
similarity index 100%
rename from integration-testing/multi-nodejs-test/run-handlebars.js
rename to tests/integration/multi-nodejs-test/run-handlebars.js
diff --git a/integration-testing/multi-nodejs-test/test.sh b/tests/integration/multi-nodejs-test/test.sh
similarity index 78%
rename from integration-testing/multi-nodejs-test/test.sh
rename to tests/integration/multi-nodejs-test/test.sh
index ac027f1a3..03fa7da60 100755
--- a/integration-testing/multi-nodejs-test/test.sh
+++ b/tests/integration/multi-nodejs-test/test.sh
@@ -7,9 +7,6 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
 
 # This script tests with precompiler and the built distribution with multiple NodeJS version.
-# The rest of the travis-build will only work with newer NodeJS versions, because the build
-# tools don't support older versions.
-# However, the built distribution should work with older NodeJS versions as well.
 # This test is simple by design. It merely ensures, that calling Handlebars does not fail with old versions.
 # It does (almost) not test for correctness, because that is already done in the mocha-tests.
 # And it does not use any NodeJS based testing framework to make this part independent of the Node version.
@@ -17,7 +14,7 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
 unset npm_config_prefix
 
 echo "Handlebars should be able to run in various versions of NodeJS"
-for node_version_to_test in 10 11 12 13 14 15; do
+for node_version_to_test in 10 11 12 13 14 15 16 17; do
 
     rm target node_modules package-lock.json -rf
     mkdir target
diff --git a/tests/integration/rollup-test/.eslintrc.js b/tests/integration/rollup-test/.eslintrc.js
new file mode 100644
index 000000000..506a733f4
--- /dev/null
+++ b/tests/integration/rollup-test/.eslintrc.js
@@ -0,0 +1,11 @@
+module.exports = {
+  root: true,
+  extends: ['eslint:recommended', 'prettier'],
+  env: {
+    browser: true
+  },
+  parserOptions: {
+    sourceType: 'module',
+    ecmaVersion: 6
+  }
+};
diff --git a/integration-testing/webpack-babel-test/.gitignore b/tests/integration/rollup-test/.gitignore
similarity index 100%
rename from integration-testing/webpack-babel-test/.gitignore
rename to tests/integration/rollup-test/.gitignore
diff --git a/tests/integration/rollup-test/package.json b/tests/integration/rollup-test/package.json
new file mode 100644
index 000000000..18807a7cd
--- /dev/null
+++ b/tests/integration/rollup-test/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "rollup-test",
+  "description": "Various tests with Handlebars and rollup",
+  "version": "1.0.0",
+  "scripts": {
+    "build": "rollup --config rollup.config.js --failAfterWarnings"
+  },
+  "private": true,
+  "devDependencies": {
+    "@rollup/plugin-node-resolve": "^13.1.1",
+    "handlebars": "file:../../..",
+    "rollup": "^2.61.1"
+  }
+}
diff --git a/tests/integration/rollup-test/rollup.config.js b/tests/integration/rollup-test/rollup.config.js
new file mode 100644
index 000000000..bd4a58c9d
--- /dev/null
+++ b/tests/integration/rollup-test/rollup.config.js
@@ -0,0 +1,10 @@
+import { nodeResolve } from '@rollup/plugin-node-resolve';
+
+export default {
+  input: 'src/index.js',
+  output: {
+    file: 'dist/bundle.js',
+    format: 'es'
+  },
+  plugins: [nodeResolve()]
+};
diff --git a/tests/integration/rollup-test/src/index.js b/tests/integration/rollup-test/src/index.js
new file mode 100644
index 000000000..2929870d9
--- /dev/null
+++ b/tests/integration/rollup-test/src/index.js
@@ -0,0 +1,8 @@
+import Handlebars from 'handlebars/lib/handlebars';
+
+const template = Handlebars.compile('Author: {{author}}');
+const result = template({ author: 'Yehuda' });
+
+if (result !== 'Author: Yehuda') {
+  throw Error('Assertion failed');
+}
diff --git a/tests/integration/rollup-test/test.sh b/tests/integration/rollup-test/test.sh
new file mode 100755
index 000000000..dabf8043e
--- /dev/null
+++ b/tests/integration/rollup-test/test.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+# Cleanup: package-lock and "npm ci" is not working with local dependencies
+rm dist package-lock.json -rf
+npm install
+npm run build
+
+node dist/bundle.js
+echo "Success"
\ No newline at end of file
diff --git a/integration-testing/run-integration-tests.sh b/tests/integration/run-integration-tests.sh
similarity index 100%
rename from integration-testing/run-integration-tests.sh
rename to tests/integration/run-integration-tests.sh
diff --git a/integration-testing/webpack-babel-test/.babelrc b/tests/integration/webpack-babel-test/.babelrc
similarity index 100%
rename from integration-testing/webpack-babel-test/.babelrc
rename to tests/integration/webpack-babel-test/.babelrc
diff --git a/integration-testing/webpack-test/.gitignore b/tests/integration/webpack-babel-test/.gitignore
similarity index 100%
rename from integration-testing/webpack-test/.gitignore
rename to tests/integration/webpack-babel-test/.gitignore
diff --git a/integration-testing/webpack-babel-test/package.json b/tests/integration/webpack-babel-test/package.json
similarity index 94%
rename from integration-testing/webpack-babel-test/package.json
rename to tests/integration/webpack-babel-test/package.json
index dd21ae0af..14dd6176f 100644
--- a/integration-testing/webpack-babel-test/package.json
+++ b/tests/integration/webpack-babel-test/package.json
@@ -12,7 +12,7 @@
     "@roundingwellos/babel-plugin-handlebars-inline-precompile": "^3.0.1",
     "babel-loader": "^8.0.6",
     "babel-plugin-istanbul": "^5.2.0",
-    "handlebars": "file:../..",
+    "handlebars": "file:../../..",
     "handlebars-loader": "^1.7.1",
     "nyc": "^14.1.1",
     "webpack": "^4.39.3",
diff --git a/tests/integration/webpack-babel-test/src/.eslintrc.js b/tests/integration/webpack-babel-test/src/.eslintrc.js
new file mode 100644
index 000000000..506a733f4
--- /dev/null
+++ b/tests/integration/webpack-babel-test/src/.eslintrc.js
@@ -0,0 +1,11 @@
+module.exports = {
+  root: true,
+  extends: ['eslint:recommended', 'prettier'],
+  env: {
+    browser: true
+  },
+  parserOptions: {
+    sourceType: 'module',
+    ecmaVersion: 6
+  }
+};
diff --git a/integration-testing/webpack-babel-test/src/handlebars-inline-precompile-test.js b/tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js
similarity index 100%
rename from integration-testing/webpack-babel-test/src/handlebars-inline-precompile-test.js
rename to tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js
diff --git a/integration-testing/webpack-babel-test/src/lib/assert.js b/tests/integration/webpack-babel-test/src/lib/assert.js
similarity index 100%
rename from integration-testing/webpack-babel-test/src/lib/assert.js
rename to tests/integration/webpack-babel-test/src/lib/assert.js
diff --git a/integration-testing/webpack-babel-test/test.sh b/tests/integration/webpack-babel-test/test.sh
similarity index 100%
rename from integration-testing/webpack-babel-test/test.sh
rename to tests/integration/webpack-babel-test/test.sh
diff --git a/integration-testing/webpack-babel-test/webpack.config.js b/tests/integration/webpack-babel-test/webpack.config.js
similarity index 100%
rename from integration-testing/webpack-babel-test/webpack.config.js
rename to tests/integration/webpack-babel-test/webpack.config.js
diff --git a/tests/integration/webpack-test/.gitignore b/tests/integration/webpack-test/.gitignore
new file mode 100644
index 000000000..3a8ec2b3f
--- /dev/null
+++ b/tests/integration/webpack-test/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+dist
+package-lock.json
\ No newline at end of file
diff --git a/integration-testing/webpack-test/package.json b/tests/integration/webpack-test/package.json
similarity index 92%
rename from integration-testing/webpack-test/package.json
rename to tests/integration/webpack-test/package.json
index 0940fe5f7..d01e12d83 100644
--- a/integration-testing/webpack-test/package.json
+++ b/tests/integration/webpack-test/package.json
@@ -13,7 +13,7 @@
   "license": "ISC",
   "dependencies": {},
   "devDependencies": {
-    "handlebars": "file:../..",
+    "handlebars": "file:../../..",
     "handlebars-loader": "^1.7.1",
     "webpack": "^4.39.3",
     "webpack-cli": "^3.3.7"
diff --git a/tests/integration/webpack-test/src/.eslintrc.js b/tests/integration/webpack-test/src/.eslintrc.js
new file mode 100644
index 000000000..091f1b915
--- /dev/null
+++ b/tests/integration/webpack-test/src/.eslintrc.js
@@ -0,0 +1,12 @@
+module.exports = {
+  root: true,
+  extends: ['eslint:recommended', 'prettier'],
+  env: {
+    node: true,
+    browser: true
+  },
+  parserOptions: {
+    sourceType: 'module',
+    ecmaVersion: 6
+  }
+};
diff --git a/integration-testing/webpack-test/src/handlebars-default-import-pre-4.2-test.js b/tests/integration/webpack-test/src/handlebars-default-import-pre-4.2-test.js
similarity index 100%
rename from integration-testing/webpack-test/src/handlebars-default-import-pre-4.2-test.js
rename to tests/integration/webpack-test/src/handlebars-default-import-pre-4.2-test.js
diff --git a/integration-testing/webpack-test/src/handlebars-default-import-test.js b/tests/integration/webpack-test/src/handlebars-default-import-test.js
similarity index 100%
rename from integration-testing/webpack-test/src/handlebars-default-import-test.js
rename to tests/integration/webpack-test/src/handlebars-default-import-test.js
diff --git a/integration-testing/webpack-test/src/handlebars-loader-test.js b/tests/integration/webpack-test/src/handlebars-loader-test.js
similarity index 100%
rename from integration-testing/webpack-test/src/handlebars-loader-test.js
rename to tests/integration/webpack-test/src/handlebars-loader-test.js
diff --git a/integration-testing/webpack-test/src/handlebars-require-vs-import-test.js b/tests/integration/webpack-test/src/handlebars-require-vs-import-test.js
similarity index 100%
rename from integration-testing/webpack-test/src/handlebars-require-vs-import-test.js
rename to tests/integration/webpack-test/src/handlebars-require-vs-import-test.js
diff --git a/integration-testing/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js b/tests/integration/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js
similarity index 100%
rename from integration-testing/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js
rename to tests/integration/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js
diff --git a/integration-testing/webpack-test/src/handlebars-wildcard-import-test.js b/tests/integration/webpack-test/src/handlebars-wildcard-import-test.js
similarity index 100%
rename from integration-testing/webpack-test/src/handlebars-wildcard-import-test.js
rename to tests/integration/webpack-test/src/handlebars-wildcard-import-test.js
diff --git a/integration-testing/webpack-test/src/lib/assert.js b/tests/integration/webpack-test/src/lib/assert.js
similarity index 100%
rename from integration-testing/webpack-test/src/lib/assert.js
rename to tests/integration/webpack-test/src/lib/assert.js
diff --git a/integration-testing/webpack-test/src/test-template.handlebars b/tests/integration/webpack-test/src/test-template.handlebars
similarity index 100%
rename from integration-testing/webpack-test/src/test-template.handlebars
rename to tests/integration/webpack-test/src/test-template.handlebars
diff --git a/integration-testing/webpack-test/test.sh b/tests/integration/webpack-test/test.sh
similarity index 100%
rename from integration-testing/webpack-test/test.sh
rename to tests/integration/webpack-test/test.sh
diff --git a/integration-testing/webpack-test/webpack.config.js b/tests/integration/webpack-test/webpack.config.js
similarity index 100%
rename from integration-testing/webpack-test/webpack.config.js
rename to tests/integration/webpack-test/webpack.config.js
diff --git a/print-script b/tests/print-script.js
similarity index 53%
rename from print-script
rename to tests/print-script.js
index 846469871..01575a38a 100755
--- a/print-script
+++ b/tests/print-script.js
@@ -1,30 +1,30 @@
-#! /usr/bin/env node
 /* eslint-disable no-console, no-var */
 // Util script for debugging source code generation issues
 
 var script = process.argv[2].replace(/\\n/g, '\n'),
-    verbose = process.argv[3] === '-v';
+  verbose = process.argv[3] === '-v';
 
-var Handlebars = require('./lib'),
-    SourceMap = require('source-map'),
-      SourceMapConsumer = SourceMap.SourceMapConsumer;
+var Handlebars = require('./../lib'),
+  SourceMap = require('source-map'),
+  SourceMapConsumer = SourceMap.SourceMapConsumer;
 
 var template = Handlebars.precompile(script, {
-      srcName: 'input.hbs',
-      destName: 'output.js',
+  srcName: 'input.hbs',
+  destName: 'output.js',
 
-      assumeObjects: true,
-      compat: false,
-      strict: true,
-      knownHelpersOnly: false
-    });
+  assumeObjects: true,
+  compat: false,
+  strict: true,
+  trackIds: true,
+  knownHelpersOnly: false
+});
 
 if (!verbose) {
   console.log(template);
 } else {
   var consumer = new SourceMapConsumer(template.map),
-      lines = template.code.split('\n'),
-      srcLines = script.split('\n');
+    lines = template.code.split('\n'),
+    srcLines = script.split('\n');
 
   console.log();
   console.log('Source:');
@@ -42,17 +42,24 @@ if (!verbose) {
   console.log(template.map);
   console.log();
 
+  // eslint-disable-next-line no-inner-declarations
   function collectSource(lines, lineName, colName, order) {
     var ret = {},
-        ordered = [],
-        last;
+      ordered = [],
+      last;
 
     function collect(current) {
       if (last) {
-        var mapLines = lines.slice(last[lineName] - 1, current && current[lineName]);
+        var mapLines = lines.slice(
+          last[lineName] - 1,
+          current && current[lineName]
+        );
         if (mapLines.length) {
           if (current) {
-            mapLines[mapLines.length - 1] = mapLines[mapLines.length - 1].slice(0, current[colName]);
+            mapLines[mapLines.length - 1] = mapLines[mapLines.length - 1].slice(
+              0,
+              current[colName]
+            );
           }
           mapLines[0] = mapLines[0].slice(last[colName]);
         }
@@ -72,23 +79,36 @@ if (!verbose) {
     return ret;
   }
 
-  srcLines = collectSource(srcLines, 'originalLine', 'originalColumn', SourceMapConsumer.ORIGINAL_ORDER);
+  srcLines = collectSource(
+    srcLines,
+    'originalLine',
+    'originalColumn',
+    SourceMapConsumer.ORIGINAL_ORDER
+  );
   lines = collectSource(lines, 'generatedLine', 'generatedColumn');
 
   consumer.eachMapping(function(mapping) {
-    var originalSrc = srcLines[mapping.originalLine + ':' + mapping.originalColumn],
-        generatedSrc = lines[mapping.generatedLine + ':' + mapping.generatedColumn];
+    var originalSrc =
+        srcLines[mapping.originalLine + ':' + mapping.originalColumn],
+      generatedSrc =
+        lines[mapping.generatedLine + ':' + mapping.generatedColumn];
 
     if (!mapping.originalLine) {
-      console.log('generated', mapping.generatedLine + ':' + mapping.generatedColumn, generatedSrc);
+      console.log(
+        'generated',
+        mapping.generatedLine + ':' + mapping.generatedColumn,
+        generatedSrc
+      );
     } else {
-      console.log('map',
-          mapping.source,
-          mapping.originalLine + ':' + mapping.originalColumn,
-          originalSrc,
-          '->',
-          mapping.generatedLine + ':' + mapping.generatedColumn,
-          generatedSrc);
+      console.log(
+        'map',
+        mapping.source,
+        mapping.originalLine + ':' + mapping.originalColumn,
+        originalSrc,
+        '->',
+        mapping.generatedLine + ':' + mapping.generatedColumn,
+        generatedSrc
+      );
     }
   });
 }
