diff --git a/.eslintrc b/.eslintrc index f4b9e1779..4ccef9e60 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,6 @@ extends: - eslint-config-shakacode - prettier - - prettier/react plugins: - import diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 30d130baa..cb2fd23ee 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- A bug is a crash or incorrect behavior. If you have a debugging or troubleshooting question, please open [a discussion](https://github.com/shakacode/react_on_rails/discussions). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d6..2f28cead0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9d46cce19..06ced73e1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,14 +6,15 @@ these bugs have open GitHub issues, be sure to tag them here as well, to keep the conversation linked together._ ### Pull Request checklist + _Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by `~`._ - [ ] Add/update test to cover these changes - [ ] Update documentation -- [ ] Update CHANGELOG file - _Add the CHANGELOG entry at the top of the file._ +- [ ] Update CHANGELOG file + +_Add the CHANGELOG entry at the top of the file._ ### Other Information _Remove this paragraph and mention any other important and relevant information such as benchmarks._ - diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 9da9ca627..b63f440e5 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -7,7 +7,7 @@ on: pull_request: jobs: - examples: + examples: env: SKIP_YARN_COREPACK_CHECK: 0 strategy: @@ -16,83 +16,83 @@ jobs: versions: ['oldest', 'newest'] runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Get changed files - id: changed-files - uses: tj-actions/changed-files@v44 - with: - files: | - lib/generators/** - rakelib/example_type.rb - rakelib/example_config.yml - rakelib/examples.rake - rakelib/run_rspec.rake - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} - bundler: 2.5.9 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Print system information - run: | - echo "Linux release: "; cat /etc/issue - echo "Current user: "; whoami - echo "Current directory: "; pwd - echo "Ruby version: "; ruby -v - echo "Node version: "; node -v - echo "Yarn version: "; yarn --version - echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 - if: matrix.versions == 'oldest' - run: script/convert - - name: Save root node_modules to cache - uses: actions/cache@v4 - with: - path: node_modules - key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} - - name: Save root ruby gems to cache - uses: actions/cache@v4 - with: - path: vendor/bundle - key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} - - id: get-sha - run: echo "sha=\"$(git rev-parse HEAD)\"" >> "$GITHUB_OUTPUT" - - name: Install Node modules with Yarn for renderer package - run: | - yarn install --no-progress --no-emoji - sudo yarn global add yalc - - name: yalc publish for react-on-rails - run: yalc publish - - name: Install Ruby Gems for package - run: | - bundle lock --add-platform 'x86_64-linux' - if ! bundle check --path=vendor/bundle; then - bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - fi - - name: Ensure minimum required Chrome version - run: | - echo -e "Already installed $(google-chrome --version)\n" - MINIMUM_REQUIRED_CHROME_VERSION=75 - INSTALLED_CHROME_MAJOR_VERSION="$(google-chrome --version | tr ' .' '\t' | cut -f3)" - if [[ $INSTALLED_CHROME_MAJOR_VERSION < $MINIMUM_REQUIRED_CHROME_VERSION ]]; then - wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' - sudo apt-get update - sudo apt-get install google-chrome-stable - echo -e "\nInstalled $(google-chrome --version)" - fi - - name: Increase the amount of inotify watchers - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - - name: Main CI - if: steps.changed-files.outputs.any_changed == 'true' - run: bundle exec rake run_rspec:${{ matrix.versions == 'oldest' && 'web' || 'shaka' }}packer_examples - - name: Store test results - uses: actions/upload-artifact@v4 - with: - name: main-rspec-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: ~/rspec + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v44 + with: + files: | + lib/generators/** + rakelib/example_type.rb + rakelib/example_config.yml + rakelib/examples.rake + rakelib/run_rspec.rake + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} + bundler: 2.5.9 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Print system information + run: | + echo "Linux release: "; cat /etc/issue + echo "Current user: "; whoami + echo "Current directory: "; pwd + echo "Ruby version: "; ruby -v + echo "Node version: "; node -v + echo "Yarn version: "; yarn --version + echo "Bundler version: "; bundle --version + - name: run conversion script to support shakapacker v6 + if: matrix.versions == 'oldest' + run: script/convert + - name: Save root node_modules to cache + uses: actions/cache@v4 + with: + path: node_modules + key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} + - name: Save root ruby gems to cache + uses: actions/cache@v4 + with: + path: vendor/bundle + key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} + - id: get-sha + run: echo "sha=\"$(git rev-parse HEAD)\"" >> "$GITHUB_OUTPUT" + - name: Install Node modules with Yarn for renderer package + run: | + yarn install --no-progress --no-emoji + sudo yarn global add yalc + - name: yalc publish for react-on-rails + run: yalc publish + - name: Install Ruby Gems for package + run: | + bundle lock --add-platform 'x86_64-linux' + if ! bundle check --path=vendor/bundle; then + bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + fi + - name: Ensure minimum required Chrome version + run: | + echo -e "Already installed $(google-chrome --version)\n" + MINIMUM_REQUIRED_CHROME_VERSION=75 + INSTALLED_CHROME_MAJOR_VERSION="$(google-chrome --version | tr ' .' '\t' | cut -f3)" + if [[ $INSTALLED_CHROME_MAJOR_VERSION < $MINIMUM_REQUIRED_CHROME_VERSION ]]; then + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + sudo apt-get update + sudo apt-get install google-chrome-stable + echo -e "\nInstalled $(google-chrome --version)" + fi + - name: Increase the amount of inotify watchers + run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + - name: Main CI + if: steps.changed-files.outputs.any_changed == 'true' + run: bundle exec rake run_rspec:${{ matrix.versions == 'oldest' && 'web' || 'shaka' }}packer_examples + - name: Store test results + uses: actions/upload-artifact@v4 + with: + name: main-rspec-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: ~/rspec diff --git a/.github/workflows/lint-js-and-ruby.yml b/.github/workflows/lint-js-and-ruby.yml index 490d2a3e6..7e72b4e07 100644 --- a/.github/workflows/lint-js-and-ruby.yml +++ b/.github/workflows/lint-js-and-ruby.yml @@ -1,6 +1,5 @@ name: Lint JS and Ruby - on: push: branches: @@ -11,121 +10,121 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3 - bundler: 2.5.9 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Print system information - run: | - echo "Linux release: "; cat /etc/issue - echo "Current user: "; whoami - echo "Current directory: "; pwd - echo "Ruby version: "; ruby -v - echo "Node version: "; node -v - echo "Yarn version: "; yarn --version - echo "Bundler version: "; bundle --version - - name: Save root node_modules to cache - uses: actions/cache@v4 - with: - path: node_modules - key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} - - name: Save root ruby gems to cache - uses: actions/cache@v4 - with: - path: vendor/bundle - key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-oldest - - name: Install Node modules with Yarn for renderer package - run: | - yarn install --no-progress --no-emoji - sudo yarn global add yalc - - name: yalc publish for react-on-rails - run: yalc publish - - name: Save spec/dummy/node_modules to cache - uses: actions/cache@v4 - with: - path: spec/dummy/node_modules - key: dummy-app-node-modules-cache-${{ hashFiles('spec/dummy/package.json') }}-newest - - name: yalc add react-on-rails - run: cd spec/dummy && yalc add react-on-rails - - name: Install Node modules with Yarn for dummy app - run: cd spec/dummy && yarn install --no-progress --no-emoji - - name: Install Ruby Gems for package - run: bundle check --path=vendor/bundle || bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - - name: Lint Ruby - run: bundle exec rubocop - - name: Install Node modules with Yarn for dummy app - run: cd spec/dummy && yarn install --ignore-scripts --no-progress --no-emoji - - name: Save dummy app ruby gems to cache - uses: actions/cache@v4 - with: - path: spec/dummy/vendor/bundle - key: dummy-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-oldest - - name: Install Ruby Gems for dummy app - run: | - cd spec/dummy - bundle lock --add-platform 'x86_64-linux' - if ! bundle check --path=vendor/bundle; then - bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - fi - - name: generate file system-based packs - run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs - - name: Detect dead code - run: | - yarn run knip - yarn run knip --production - - name: Lint JS - run: yarn start lint - - name: Check formatting - run: yarn start format.listDifferent - - name: Type-check TypeScript - run: yarn run type-check - - name: Lint package publishing - # --profile because we don't care about node10 - # --ignore-rules CJS default export can't be resolved at the moment, - # revisit in 15.0.0 - run: yarn run attw --pack . --profile node16 --ignore-rules cjs-only-exports-default - # We only download and run Actionlint if there is any difference in GitHub Action workflows - # https://github.com/rhysd/actionlint/blob/main/docs/usage.md#on-github-actions - - name: Check for GitHub Actions changes - id: check-workflows - run: | - git fetch origin ${{ github.event.pull_request.base.sha }} - if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -q '^.github/workflows'; then - echo "changed=true" >> "$GITHUB_OUTPUT" - response=$(curl -sf https://api.github.com/repos/rhysd/actionlint/releases/latest) - if [ $? -eq 0 ]; then - actionlint_version=$(echo "$response" | jq -r .tag_name) - if [ -z "$actionlint_version" ]; then - echo "Failed to parse Actionlint version" + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3 + bundler: 2.5.9 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Print system information + run: | + echo "Linux release: "; cat /etc/issue + echo "Current user: "; whoami + echo "Current directory: "; pwd + echo "Ruby version: "; ruby -v + echo "Node version: "; node -v + echo "Yarn version: "; yarn --version + echo "Bundler version: "; bundle --version + - name: Save root node_modules to cache + uses: actions/cache@v4 + with: + path: node_modules + key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} + - name: Save root ruby gems to cache + uses: actions/cache@v4 + with: + path: vendor/bundle + key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-oldest + - name: Install Node modules with Yarn for renderer package + run: | + yarn install --no-progress --no-emoji + sudo yarn global add yalc + - name: yalc publish for react-on-rails + run: yalc publish + - name: Save spec/dummy/node_modules to cache + uses: actions/cache@v4 + with: + path: spec/dummy/node_modules + key: dummy-app-node-modules-cache-${{ hashFiles('spec/dummy/package.json') }}-newest + - name: yalc add react-on-rails + run: cd spec/dummy && yalc add react-on-rails + - name: Install Node modules with Yarn for dummy app + run: cd spec/dummy && yarn install --no-progress --no-emoji + - name: Install Ruby Gems for package + run: bundle check --path=vendor/bundle || bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + - name: Lint Ruby + run: bundle exec rubocop + - name: Install Node modules with Yarn for dummy app + run: cd spec/dummy && yarn install --ignore-scripts --no-progress --no-emoji + - name: Save dummy app ruby gems to cache + uses: actions/cache@v4 + with: + path: spec/dummy/vendor/bundle + key: dummy-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-oldest + - name: Install Ruby Gems for dummy app + run: | + cd spec/dummy + bundle lock --add-platform 'x86_64-linux' + if ! bundle check --path=vendor/bundle; then + bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + fi + - name: generate file system-based packs + run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs + - name: Detect dead code + run: | + yarn run knip + yarn run knip --production + - name: Lint JS + run: yarn start lint + - name: Check formatting + run: yarn start format.listDifferent + - name: Type-check TypeScript + run: yarn run type-check + - name: Lint package publishing + # --profile because we don't care about node10 + # --ignore-rules CJS default export can't be resolved at the moment, + # revisit in 15.0.0 + run: yarn run attw --pack . --profile node16 --ignore-rules cjs-only-exports-default + # We only download and run Actionlint if there is any difference in GitHub Action workflows + # https://github.com/rhysd/actionlint/blob/main/docs/usage.md#on-github-actions + - name: Check for GitHub Actions changes + id: check-workflows + run: | + git fetch origin ${{ github.event.pull_request.base.sha }} + if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -q '^.github/workflows'; then + echo "changed=true" >> "$GITHUB_OUTPUT" + response=$(curl -sf https://api.github.com/repos/rhysd/actionlint/releases/latest) + if [ $? -eq 0 ]; then + actionlint_version=$(echo "$response" | jq -r .tag_name) + if [ -z "$actionlint_version" ]; then + echo "Failed to parse Actionlint version" + exit 1 + fi + else + echo "Failed to fetch latest Actionlint version" exit 1 fi - else - echo "Failed to fetch latest Actionlint version" - exit 1 + echo "actionlint_version=\"$actionlint_version\"" >> "$GITHUB_OUTPUT" fi - echo "actionlint_version=\"$actionlint_version\"" >> "$GITHUB_OUTPUT" - fi - - name: Setup Actionlint - if: steps.check-workflows.outputs.changed == 'true' - uses: actions/cache@v4 - id: cache-actionlint - with: - path: ./actionlint - key: ${{ runner.os }}-actionlint-${{ steps.check-workflows.outputs.actionlint_version }} - - name: Download Actionlint - if: steps.check-workflows.outputs.changed == 'true' && steps.cache-actionlint.outputs.cache-hit != 'true' - run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - - name: Lint GitHub Actions - if: steps.check-workflows.outputs.changed == 'true' - run: | - echo "::add-matcher::.github/actionlint-matcher.json" - SHELLCHECK_OPTS="-S warning" ./actionlint -color - shell: bash + - name: Setup Actionlint + if: steps.check-workflows.outputs.changed == 'true' + uses: actions/cache@v4 + id: cache-actionlint + with: + path: ./actionlint + key: ${{ runner.os }}-actionlint-${{ steps.check-workflows.outputs.actionlint_version }} + - name: Download Actionlint + if: steps.check-workflows.outputs.changed == 'true' && steps.cache-actionlint.outputs.cache-hit != 'true' + run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) + - name: Lint GitHub Actions + if: steps.check-workflows.outputs.changed == 'true' + run: | + echo "::add-matcher::.github/actionlint-matcher.json" + SHELLCHECK_OPTS="-S warning" ./actionlint -color + shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b22a1cef6..e22faee4d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,77 +13,77 @@ jobs: versions: ['oldest', 'newest'] runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} - bundler: 2.5.9 - # libyaml-dev is needed for psych v5 - # this gem depends on sdoc which depends on rdoc which depends on psych - - name: Fix dependency for libyaml-dev - run: sudo apt install libyaml-dev - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }} - - name: Print system information - run: | - echo "Linux release: "; cat /etc/issue - echo "Current user: "; whoami - echo "Current directory: "; pwd - echo "Ruby version: "; ruby -v - echo "Node version: "; node -v - echo "Yarn version: "; yarn --version - echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 - if: matrix.versions == 'oldest' - run: script/convert - - name: Save root node_modules to cache - uses: actions/cache@v4 - with: - path: node_modules - key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} - - name: Install Node modules with Yarn for renderer package - run: | - yarn install --no-progress --no-emoji - sudo yarn global add yalc - - name: yalc publish for react-on-rails - run: yalc publish - - name: Save spec/dummy/node_modules to cache - uses: actions/cache@v4 - with: - path: spec/dummy/node_modules - key: dummy-app-node-modules-cache-${{ hashFiles('spec/dummy/package.json') }}-${{ matrix.versions }} - - name: yalc add react-on-rails - run: cd spec/dummy && yalc add react-on-rails - - name: Install Node modules with Yarn for dummy app - run: cd spec/dummy && yarn install --no-progress --no-emoji - - name: Save dummy app ruby gems to cache - uses: actions/cache@v4 - with: - path: spec/dummy/vendor/bundle - key: dummy-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} - - name: Install Ruby Gems for dummy app - run: | - cd spec/dummy - bundle lock --add-platform 'x86_64-linux' - if ! bundle check --path=vendor/bundle; then - bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - fi - - name: generate file system-based packs - run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs - - name: Build test bundles for dummy app - run: cd spec/dummy && rm -rf public/webpack/test && yarn run build:rescript && RAILS_ENV="test" NODE_ENV="test" bin/${{ matrix.versions == 'oldest' && 'web' || 'shaka' }}packer - - id: get-sha - run: echo "sha=\"$(git rev-parse HEAD)\"" >> "$GITHUB_OUTPUT" - - name: Save test webpack bundles to cache (for build number checksum used by rspec job) - uses: actions/cache/save@v4 - with: - path: spec/dummy/public/webpack - key: dummy-app-webpack-bundle-${{ steps.get-sha.outputs.sha }}-${{ matrix.versions }} + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} + bundler: 2.5.9 + # libyaml-dev is needed for psych v5 + # this gem depends on sdoc which depends on rdoc which depends on psych + - name: Fix dependency for libyaml-dev + run: sudo apt install libyaml-dev + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }} + - name: Print system information + run: | + echo "Linux release: "; cat /etc/issue + echo "Current user: "; whoami + echo "Current directory: "; pwd + echo "Ruby version: "; ruby -v + echo "Node version: "; node -v + echo "Yarn version: "; yarn --version + echo "Bundler version: "; bundle --version + - name: run conversion script to support shakapacker v6 + if: matrix.versions == 'oldest' + run: script/convert + - name: Save root node_modules to cache + uses: actions/cache@v4 + with: + path: node_modules + key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} + - name: Install Node modules with Yarn for renderer package + run: | + yarn install --no-progress --no-emoji + sudo yarn global add yalc + - name: yalc publish for react-on-rails + run: yalc publish + - name: Save spec/dummy/node_modules to cache + uses: actions/cache@v4 + with: + path: spec/dummy/node_modules + key: dummy-app-node-modules-cache-${{ hashFiles('spec/dummy/package.json') }}-${{ matrix.versions }} + - name: yalc add react-on-rails + run: cd spec/dummy && yalc add react-on-rails + - name: Install Node modules with Yarn for dummy app + run: cd spec/dummy && yarn install --no-progress --no-emoji + - name: Save dummy app ruby gems to cache + uses: actions/cache@v4 + with: + path: spec/dummy/vendor/bundle + key: dummy-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} + - name: Install Ruby Gems for dummy app + run: | + cd spec/dummy + bundle lock --add-platform 'x86_64-linux' + if ! bundle check --path=vendor/bundle; then + bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + fi + - name: generate file system-based packs + run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs + - name: Build test bundles for dummy app + run: cd spec/dummy && rm -rf public/webpack/test && yarn run build:rescript && RAILS_ENV="test" NODE_ENV="test" bin/${{ matrix.versions == 'oldest' && 'web' || 'shaka' }}packer + - id: get-sha + run: echo "sha=\"$(git rev-parse HEAD)\"" >> "$GITHUB_OUTPUT" + - name: Save test webpack bundles to cache (for build number checksum used by rspec job) + uses: actions/cache/save@v4 + with: + path: spec/dummy/public/webpack + key: dummy-app-webpack-bundle-${{ steps.get-sha.outputs.sha }}-${{ matrix.versions }} dummy-app-integration-tests: needs: build-dummy-app-webpack-test-bundles @@ -93,122 +93,122 @@ jobs: versions: ['oldest', 'newest'] runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} - bundler: 2.5.9 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }} - - name: Print system information - run: | - echo "Linux release: "; cat /etc/issue - echo "Current user: "; whoami - echo "Current directory: "; pwd - echo "Ruby version: "; ruby -v - echo "Node version: "; node -v - echo "Yarn version: "; yarn --version - echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 - if: matrix.versions == 'oldest' - run: script/convert - - name: Save root node_modules to cache - uses: actions/cache@v4 - with: - path: node_modules - key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} - - name: Save root ruby gems to cache - uses: actions/cache@v4 - with: - path: vendor/bundle - key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} - - name: Save dummy app ruby gems to cache - uses: actions/cache@v4 - with: - path: spec/dummy/vendor/bundle - key: dummy-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} - - name: Save spec/dummy/node_modules to cache - uses: actions/cache@v4 - with: - path: spec/dummy/node_modules - key: dummy-app-node-modules-cache-${{ hashFiles('spec/dummy/package.json') }}-${{ matrix.versions }} - - id: get-sha - run: echo "sha=\"$(git rev-parse HEAD)\"" >> "$GITHUB_OUTPUT" - - name: Save test webpack bundles to cache (for build number checksum used by rspec job) - uses: actions/cache@v4 - with: - path: spec/dummy/public/webpack - key: dummy-app-webpack-bundle-${{ steps.get-sha.outputs.sha }}-${{ matrix.versions }} - - name: Install Node modules with Yarn - run: | - yarn install --no-progress --no-emoji - sudo yarn global add yalc - - name: yalc publish for react-on-rails - run: yalc publish - - name: yalc add react-on-rails - run: cd spec/dummy && yalc add react-on-rails - - name: Install Node modules with Yarn for dummy app - run: cd spec/dummy && yarn install --no-progress --no-emoji - - name: Install Ruby Gems for package - run: | - bundle lock --add-platform 'x86_64-linux' - if ! bundle check --path=vendor/bundle; then - bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - fi - - name: Install Ruby Gems for dummy app - run: | - cd spec/dummy - bundle lock --add-platform 'x86_64-linux' - if ! bundle check --path=vendor/bundle; then - bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - fi - - name: Ensure minimum required Chrome version - run: | - echo -e "Already installed $(google-chrome --version)\n" - MINIMUM_REQUIRED_CHROME_VERSION=75 - INSTALLED_CHROME_MAJOR_VERSION="$(google-chrome --version | tr ' .' '\t' | cut -f3)" - if [[ $INSTALLED_CHROME_MAJOR_VERSION -lt $MINIMUM_REQUIRED_CHROME_VERSION ]]; then - wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' - sudo apt-get update - sudo apt-get install google-chrome-stable - echo -e "\nInstalled $(google-chrome --version)" - fi - - name: Increase the amount of inotify watchers - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - - name: generate file system-based packs - run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs - - name: Git Stuff - if: matrix.versions == 'oldest' - run: | - git config user.email "you@example.com" - git config user.name "Your Name" - git commit -am "stop generators from complaining about uncommitted code" - - run: cd spec/dummy && bundle info shakapacker - - name: Main CI - run: bundle exec rake run_rspec:all_dummy - - name: Store test results - uses: actions/upload-artifact@v4 - with: - name: main-rspec-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: ~/rspec - - name: Store artifacts - uses: actions/upload-artifact@v4 - with: - name: dummy-app-capybara-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: spec/dummy/tmp/capybara - - name: Store artifacts - uses: actions/upload-artifact@v4 - with: - name: dummy-app-test-log-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: spec/dummy/log/test.log - - name: Store artifacts - uses: actions/upload-artifact@v4 - with: - name: dummy-app-yarn-log-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: spec/dummy/yarn-error.log + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} + bundler: 2.5.9 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }} + - name: Print system information + run: | + echo "Linux release: "; cat /etc/issue + echo "Current user: "; whoami + echo "Current directory: "; pwd + echo "Ruby version: "; ruby -v + echo "Node version: "; node -v + echo "Yarn version: "; yarn --version + echo "Bundler version: "; bundle --version + - name: run conversion script to support shakapacker v6 + if: matrix.versions == 'oldest' + run: script/convert + - name: Save root node_modules to cache + uses: actions/cache@v4 + with: + path: node_modules + key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} + - name: Save root ruby gems to cache + uses: actions/cache@v4 + with: + path: vendor/bundle + key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} + - name: Save dummy app ruby gems to cache + uses: actions/cache@v4 + with: + path: spec/dummy/vendor/bundle + key: dummy-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ hashFiles('Gemfile.development_dependencies') }}-${{ matrix.versions }} + - name: Save spec/dummy/node_modules to cache + uses: actions/cache@v4 + with: + path: spec/dummy/node_modules + key: dummy-app-node-modules-cache-${{ hashFiles('spec/dummy/package.json') }}-${{ matrix.versions }} + - id: get-sha + run: echo "sha=\"$(git rev-parse HEAD)\"" >> "$GITHUB_OUTPUT" + - name: Save test webpack bundles to cache (for build number checksum used by rspec job) + uses: actions/cache@v4 + with: + path: spec/dummy/public/webpack + key: dummy-app-webpack-bundle-${{ steps.get-sha.outputs.sha }}-${{ matrix.versions }} + - name: Install Node modules with Yarn + run: | + yarn install --no-progress --no-emoji + sudo yarn global add yalc + - name: yalc publish for react-on-rails + run: yalc publish + - name: yalc add react-on-rails + run: cd spec/dummy && yalc add react-on-rails + - name: Install Node modules with Yarn for dummy app + run: cd spec/dummy && yarn install --no-progress --no-emoji + - name: Install Ruby Gems for package + run: | + bundle lock --add-platform 'x86_64-linux' + if ! bundle check --path=vendor/bundle; then + bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + fi + - name: Install Ruby Gems for dummy app + run: | + cd spec/dummy + bundle lock --add-platform 'x86_64-linux' + if ! bundle check --path=vendor/bundle; then + bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + fi + - name: Ensure minimum required Chrome version + run: | + echo -e "Already installed $(google-chrome --version)\n" + MINIMUM_REQUIRED_CHROME_VERSION=75 + INSTALLED_CHROME_MAJOR_VERSION="$(google-chrome --version | tr ' .' '\t' | cut -f3)" + if [[ $INSTALLED_CHROME_MAJOR_VERSION -lt $MINIMUM_REQUIRED_CHROME_VERSION ]]; then + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + sudo apt-get update + sudo apt-get install google-chrome-stable + echo -e "\nInstalled $(google-chrome --version)" + fi + - name: Increase the amount of inotify watchers + run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + - name: generate file system-based packs + run: cd spec/dummy && RAILS_ENV="test" bundle exec rake react_on_rails:generate_packs + - name: Git Stuff + if: matrix.versions == 'oldest' + run: | + git config user.email "you@example.com" + git config user.name "Your Name" + git commit -am "stop generators from complaining about uncommitted code" + - run: cd spec/dummy && bundle info shakapacker + - name: Main CI + run: bundle exec rake run_rspec:all_dummy + - name: Store test results + uses: actions/upload-artifact@v4 + with: + name: main-rspec-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: ~/rspec + - name: Store artifacts + uses: actions/upload-artifact@v4 + with: + name: dummy-app-capybara-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: spec/dummy/tmp/capybara + - name: Store artifacts + uses: actions/upload-artifact@v4 + with: + name: dummy-app-test-log-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: spec/dummy/log/test.log + - name: Store artifacts + uses: actions/upload-artifact@v4 + with: + name: dummy-app-yarn-log-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: spec/dummy/yarn-error.log diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index 81d4e4ce4..dae233fbf 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -1,6 +1,5 @@ name: JS unit tests for Renderer package - on: push: branches: @@ -14,32 +13,32 @@ jobs: versions: ['oldest', 'newest'] runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }} - - name: Print system information - run: | - echo "Linux release: "; cat /etc/issue - echo "Current user: "; whoami - echo "Current directory: "; pwd - echo "Node version: "; node -v - echo "Yarn version: "; yarn --version - - name: Save root node_modules to cache - uses: actions/cache@v4 - with: - path: node_modules - key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} - - name: run conversion script - if: matrix.versions == 'oldest' - run: script/convert - - name: Install Node modules with Yarn for renderer package - run: | - yarn install --no-progress --no-emoji - yarn run eslint -v - sudo yarn global add yalc - - name: Run JS unit tests for Renderer package - run: yarn test + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }} + - name: Print system information + run: | + echo "Linux release: "; cat /etc/issue + echo "Current user: "; whoami + echo "Current directory: "; pwd + echo "Node version: "; node -v + echo "Yarn version: "; yarn --version + - name: Save root node_modules to cache + uses: actions/cache@v4 + with: + path: node_modules + key: v5-package-node-modules-cache-${{ hashFiles('yarn.lock') }} + - name: run conversion script + if: matrix.versions == 'oldest' + run: script/convert + - name: Install Node modules with Yarn for renderer package + run: | + yarn install --no-progress --no-emoji + yarn run eslint -v + sudo yarn global add yalc + - name: Run JS unit tests for Renderer package + run: yarn test diff --git a/.github/workflows/rspec-package-specs.yml b/.github/workflows/rspec-package-specs.yml index f91009741..e0b511308 100644 --- a/.github/workflows/rspec-package-specs.yml +++ b/.github/workflows/rspec-package-specs.yml @@ -14,51 +14,51 @@ jobs: versions: ['oldest', 'newest'] runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} - bundler: 2.5.9 - - name: Print system information - run: | - echo "Linux release: "; cat /etc/issue - echo "Current user: "; whoami - echo "Current directory: "; pwd - echo "Ruby version: "; ruby -v - echo "Node version: "; node -v - echo "Yarn version: "; yarn --version - echo "Bundler version: "; bundle --version - - name: run conversion script to support shakapacker v6 - if: matrix.versions == 'oldest' - run: script/convert - - name: Save root ruby gems to cache - uses: actions/cache@v4 - with: - path: vendor/bundle - key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ matrix.versions }} - - name: Install Ruby Gems for package - run: bundle check --path=vendor/bundle || bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 - - name: Git Stuff - if: matrix.versions == 'oldest' - run: | - git config user.email "you@example.com" - git config user.name "Your Name" - git commit -am "stop generators from complaining about uncommitted code" - - name: Set packer version environment variable - run: | - echo "CI_PACKER_VERSION=${{ matrix.versions == 'oldest' && 'old' || 'new' }}" >> $GITHUB_ENV - - name: Run rspec tests - run: bundle exec rspec spec/react_on_rails - - name: Store test results - uses: actions/upload-artifact@v4 - with: - name: main-rspec-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: ~/rspec - - name: Store artifacts - uses: actions/upload-artifact@v4 - with: - name: main-test-log-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} - path: log/test.log + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.versions == 'oldest' && '3.0' || '3.3' }} + bundler: 2.5.9 + - name: Print system information + run: | + echo "Linux release: "; cat /etc/issue + echo "Current user: "; whoami + echo "Current directory: "; pwd + echo "Ruby version: "; ruby -v + echo "Node version: "; node -v + echo "Yarn version: "; yarn --version + echo "Bundler version: "; bundle --version + - name: run conversion script to support shakapacker v6 + if: matrix.versions == 'oldest' + run: script/convert + - name: Save root ruby gems to cache + uses: actions/cache@v4 + with: + path: vendor/bundle + key: package-app-gem-cache-${{ hashFiles('react_on_rails.gemspec') }}-${{ matrix.versions }} + - name: Install Ruby Gems for package + run: bundle check --path=vendor/bundle || bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3 + - name: Git Stuff + if: matrix.versions == 'oldest' + run: | + git config user.email "you@example.com" + git config user.name "Your Name" + git commit -am "stop generators from complaining about uncommitted code" + - name: Set packer version environment variable + run: | + echo "CI_PACKER_VERSION=${{ matrix.versions == 'oldest' && 'old' || 'new' }}" >> $GITHUB_ENV + - name: Run rspec tests + run: bundle exec rspec spec/react_on_rails + - name: Store test results + uses: actions/upload-artifact@v4 + with: + name: main-rspec-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: ~/rspec + - name: Store artifacts + uses: actions/upload-artifact@v4 + with: + name: main-test-log-${{ github.run_id }}-${{ github.job }}-${{ matrix.versions }} + path: log/test.log diff --git a/.prettierignore b/.prettierignore index 2bd6090b5..772bc371a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,5 +10,14 @@ bundle/ spec/dummy/lib/bs/** spec/dummy/public **/.yalc/** -**/generated/** +**/*generated* *.res.js + +# Prettier doesn't understand ERB syntax in YAML files +.rubocop.yml +# Intentionally invalid +spec/react_on_rails/fixtures/i18n/locales_symbols/ + +# Weirdly, fixing this file creates linting errors, even though it shouldn't make a difference. +# Resolve later when upgrading ESLint. +.eslintrc diff --git a/.prettierrc b/.prettierrc index 4ad6f5e58..be64f7c35 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,16 +5,19 @@ semi: true singleQuote: true trailingComma: all bracketSpacing: true -jsxBracketSameLine: false -parser: flow +bracketSameLine: false overrides: -- files: "*.@(css|scss)" - options: - parser: css - singleQuote: false - printWidth: 120 -- files: "*.@(json)" - options: - parser: json - printWidth: 100 + - files: '*.@(css|scss)' + options: + singleQuote: false + printWidth: 120 + - files: '*.@(json)' + options: + printWidth: 100 + - files: '.*rc' + excludeFiles: + # Direnv file, not YAML + - '.envrc' + options: + parser: yaml diff --git a/.scss-lint.yml b/.scss-lint.yml index ea6f1306e..566c88008 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -5,47 +5,47 @@ scss_files: exclude: - 'spec/dummy/app/assets/stylesheets/application.css' linters: -# BangFormat: -# enabled: true -# space_before_bang: true -# space_after_bang: false -# -# BorderZero: -# enabled: true -# convention: zero # or `none` -# + # BangFormat: + # enabled: true + # space_before_bang: true + # space_after_bang: false + # + # BorderZero: + # enabled: true + # convention: zero # or `none` + # ColorKeyword: enabled: false ColorVariable: enabled: false -# -# Comment: -# enabled: true -# -# DebugStatement: -# enabled: true -# -# DeclarationOrder: -# enabled: true -# -# DuplicateProperty: -# enabled: true -# -# ElsePlacement: -# enabled: true -# style: same_line # or 'new_line' -# -# EmptyLineBetweenBlocks: -# enabled: true -# ignore_single_line_blocks: true -# -# EmptyRule: -# enabled: true -# -# FinalNewline: -# enabled: true -# present: true -# + # + # Comment: + # enabled: true + # + # DebugStatement: + # enabled: true + # + # DeclarationOrder: + # enabled: true + # + # DuplicateProperty: + # enabled: true + # + # ElsePlacement: + # enabled: true + # style: same_line # or 'new_line' + # + # EmptyLineBetweenBlocks: + # enabled: true + # ignore_single_line_blocks: true + # + # EmptyRule: + # enabled: true + # + # FinalNewline: + # enabled: true + # present: true + # HexLength: enabled: true style: long @@ -53,27 +53,27 @@ linters: HexNotation: enabled: true style: uppercase -# -# HexValidation: -# enabled: true -# + # + # HexValidation: + # enabled: true + # IdSelector: enabled: true -# -# ImportantRule: -# enabled: true -# -# ImportPath: -# enabled: true -# leading_underscore: false -# filename_extension: false -# -# Indentation: -# enabled: true -# allow_non_nested_indentation: false -# character: space # or 'tab' -# width: 2 -# + # + # ImportantRule: + # enabled: true + # + # ImportPath: + # enabled: true + # leading_underscore: false + # filename_extension: false + # + # Indentation: + # enabled: true + # allow_non_nested_indentation: false + # character: space # or 'tab' + # width: 2 + # LeadingZero: enabled: true style: include_zero diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4901109..2eec2bfe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,41 @@ # Change Log -All notable changes to this project's source code will be documented in this file. Items under `Unreleased` is upcoming features that will be out in the next version. + +All notable changes to this project's source code will be documented in this file. Items under `Unreleased` is upcoming features that will be out in the next version. Migration instructions for the major updates can be found [here](https://www.shakacode.com/react-on-rails/docs/guides/upgrading-react-on-rails#upgrading-to-version-9.md). Some smaller migration information can be found here. ## Want to Save Time Updating? -If you need help upgrading `react_on_rails`, `webpacker` to `shakapacker`, or JS packages, contact justin@shakacode.com. We can upgrade your project and improve your development and customer experiences, allowing you to focus on building new features or fixing bugs instead. +If you need help upgrading `react_on_rails`, `webpacker` to `shakapacker`, or JS packages, contact justin@shakacode.com. We can upgrade your project and improve your development and customer experiences, allowing you to focus on building new features or fixing bugs instead. For an overview of working with us, see our [Client Engagement Model](https://www.shakacode.com/blog/client-engagement-model/) article and [how we bill for time](https://www.shakacode.com/blog/shortcut-jira-trello-github-toggl-time-and-task-tracking/). If you think ShakaCode can help your project, [click here](https://meetings.hubspot.com/justingordon/30-minute-consultation) to book a call with [Justin Gordon](mailto:justin@shakacode.com), the creator of React on Rails and Shakapacker. ## Contributors + Please follow the recommendations outlined at [keepachangelog.com](http://keepachangelog.com/). Please use the existing headings and styling as a guide, and add a link for the version diff at the bottom of the file. Also, please update the `Unreleased` link to compare to the latest release version. ## Versions + ### [15.0.0-alpha.2] - 2025-03-07 + Changes since the last non-beta release. See [Release Notes](docs/release-notes/15.0.0.md) for full details. #### Added + - React Server Components Support (Pro Feature) [PR 1644](https://github.com/shakacode/react_on_rails/pull/1644) by [AbanoubGhadban](https://github.com/AbanoubGhadban). - Improved component and store hydration performance [PR 1656](https://github.com/shakacode/react_on_rails/pull/1656) by [AbanoubGhadban](https://github.com/AbanoubGhadban). +#### Removed + +- Support for React 16 and 17. [PR 1710](https://github.com/shakacode/react_on_rails/pull/1710) by [alexeyr-ci](https://github.com/alexeyr-ci). + #### Breaking Changes + +- React >=18 is now required - `ReactOnRails.reactOnRailsPageLoaded` is now an async function - `force_load` configuration now defaults to `true` - `defer_generated_component_packs` configuration now defaults to `false` @@ -32,14 +43,17 @@ See [Release Notes](docs/release-notes/15.0.0.md) for full details. ### [14.2.0] - 2025-03-03 #### Added + - Add export option 'react-on-rails/client' to avoid shipping server-rendering code to browsers (~5KB improvement) [PR 1697](https://github.com/shakacode/react_on_rails/pull/1697) by [Romex91](https://github.com/Romex91). #### Fixed + - Fix obscure errors by introducing FULL_TEXT_ERRORS [PR 1695](https://github.com/shakacode/react_on_rails/pull/1695) by [Romex91](https://github.com/Romex91). - Disable `esModuleInterop` to increase interoperability [PR 1699](https://github.com/shakacode/react_on_rails/pull/1699) by [alexeyr-ci](https://github.com/alexeyr-ci). - Resolved 14.1.1 incompatibility with eslint & made sure that spec/dummy is linted by eslint. [PR 1693](https://github.com/shakacode/react_on_rails/pull/1693) by [judahmeek](https://github.com/judahmeek). #### Changed + - More up-to-date TS config [PR 1700](https://github.com/shakacode/react_on_rails/pull/1700) by [alexeyr-ci](https://github.com/alexeyr-ci). ### [14.1.1] - 2025-01-15 @@ -59,6 +73,7 @@ See [Release Notes](docs/release-notes/15.0.0.md) for full details. - Enable use as a `git:` dependency. [PR 1664](https://github.com/shakacode/react_on_rails/pull/1664) by [alexeyr-ci](https://github.com/alexeyr-ci). #### Added + - Added streaming server rendering support: - [PR #1633](https://github.com/shakacode/react_on_rails/pull/1633) by [AbanoubGhadban](https://github.com/AbanoubGhadban). - New `stream_react_component` helper for adding streamed components to views @@ -69,23 +84,29 @@ See [Release Notes](docs/release-notes/15.0.0.md) for full details. - Added support for passing options to `YAML.safe_load` when loading locale files with `config.i18n_yml_safe_load_options`. [PR #1668](https://github.com/shakacode/react_on_rails/pull/1668) by [dzirtusss](https://github.com/dzirtusss). #### Changed + - Console replay script generation now awaits the render request promise before generating, allowing it to capture console logs from asynchronous operations. This requires using a version of the Node renderer that supports replaying async console logs. [PR #1649](https://github.com/shakacode/react_on_rails/pull/1649) by [AbanoubGhadban](https://github.com/AbanoubGhadban). ### [14.0.5] - 2024-08-20 + #### Fixed + - Should force load react-components which send over turbo-stream [PR #1620](https://github.com/shakacode/react_on_rails/pull/1620) by [theforestvn88](https://github.com/theforestvn88). ### [14.0.4] - 2024-07-02 #### Improved + - Improved dependency management by integrating package_json. [PR 1639](https://github.com/shakacode/react_on_rails/pull/1639) by [vaukalak](https://github.com/vaukalak). #### Changed + - Update outdated GitHub Actions to use Node.js 20.0 versions instead [PR 1623](https://github.com/shakacode/react_on_rails/pull/1623) by [adriangohjw](https://github.com/adriangohjw). ### [14.0.3] - 2024-06-28 #### Fixed + - Fixed css-loader installation with [PR 1634](https://github.com/shakacode/react_on_rails/pull/1634) by [vaukalak](https://github.com/vaukalak). - Address a number of typos and grammar mistakes [PR 1631](https://github.com/shakacode/react_on_rails/pull/1631) by [G-Rath](https://github.com/G-Rath). - Adds an adapter module & improves test suite to support all versions of Shakapacker. [PR 1622](https://github.com/shakacode/react_on_rails/pull/1622) by [adriangohjw](https://github.com/adriangohjw) and [judahmeek](https://github.com/judahmeek). @@ -93,96 +114,121 @@ See [Release Notes](docs/release-notes/15.0.0.md) for full details. ### [14.0.2] - 2024-06-11 #### Fixed + - Generator errors with Shakapacker v8+ fixed [PR 1629](https://github.com/shakacode/react_on_rails/pull/1629) by [vaukalak](https://github.com/vaukalak) ### [14.0.1] - 2024-05-16 #### Fixed + - Pack Generation: Added functionality that will add an import statement, if missing, to the server bundle entry point even if the auto-bundle generated files still exist [PR 1610](https://github.com/shakacode/react_on_rails/pull/1610) by [judahmeek](https://github.com/judahmeek). ### [14.0.0] - 2024-04-03 + _Major bump because dropping support for Ruby 2.7 and deprecated `webpackConfigLoader.js`._ #### Removed + - Dropped Ruby 2.7 support [PR 1595](https://github.com/shakacode/react_on_rails/pull/1595) by [ahangarha](https://github.com/ahangarha). - Removed deprecated `webpackConfigLoader.js` [PR 1600](https://github.com/shakacode/react_on_rails/pull/1600) by [ahangarha](https://github.com/ahangarha). #### Fixed + - Trimmed the Gem to remove package.json which could cause superflous security warnings. [PR 1605](https://github.com/shakacode/react_on_rails/pull/1605) by [justin808](https://github.com/justin808). - Prevent displaying the deprecation message for using `webpacker_precompile?` method and `webpacker:clean` rake task when using Shakapacker v7+ [PR 1592](https://github.com/shakacode/react_on_rails/pull/1592) by [ahangarha](https://github.com/ahangarha). - Fixed Typescript types for ServerRenderResult, ReactComponent, RenderFunction, and RailsContext interfaces. [PR 1582](https://github.com/shakacode/react_on_rails/pull/1582) & [PR 1585](https://github.com/shakacode/react_on_rails/pull/1585) by [kotarella1110](https://github.com/kotarella1110) - Removed a workaround in `JsonOutput#escape` for an no-longer supported Rails version. Additionally, removed `Utils.rails_version_less_than_4_1_1` -which was only used in the workaround. [PR 1580](https://github.com/shakacode/react_on_rails/pull/1580) by [wwahammy](https://github.com/wwahammy) + which was only used in the workaround. [PR 1580](https://github.com/shakacode/react_on_rails/pull/1580) by [wwahammy](https://github.com/wwahammy) #### Added + - Exposed TypeScript all types [PR 1586](https://github.com/shakacode/react_on_rails/pull/1586) by [kotarella1110](https://github.com/kotarella1110) ### [13.4.0] - 2023-07-30 + #### Fixed + - Fixed Pack Generation logic during `assets:precompile` if `auto_load_bundle` is `false` & `components_subdirectory` is not set. [PR 1567](https://github.com/shakacode/react_on_rails/pull/1545) by [blackjack26](https://github.com/blackjack26) & [judahmeek](https://github.com/judahmeek). #### Improved + - Improved performance by removing an unnecessary JS eval from Ruby. [PR 1544](https://github.com/shakacode/react_on_rails/pull/1544) by [wyattades](https://github.com/wyattades). #### Added + - Added support for Shakapacker 7 in install generator [PR 1548](https://github.com/shakacode/react_on_rails/pull/1548) by [ahangarha](https://github.com/ahangarha). #### Changed + - Throw error when attempting to redefine ReactOnRails. [PR 1562](https://github.com/shakacode/react_on_rails/pull/1562) by [rubenochiavone](https://github.com/rubenochiavone). - Prevent generating FS-based packs when `component_subdirectory` configuration is not present. [PR 1567](https://github.com/shakacode/react_on_rails/pull/1567) by [blackjack26](https://github.com/blackjack26). - Removed a requirement for autoloaded pack files to be generated as part of CI or deployment separate from initial Shakapacker bundling. [PR 1545](https://github.com/shakacode/react_on_rails/pull/1545) by [judahmeek](https://github.com/judahmeek). - ### [13.3.5] - 2023-05-31 + #### Fixed + - Fixed race condition where a react component could attempt to initialize before it had been registered. [PR 1540](https://github.com/shakacode/react_on_rails/pull/1540) by [judahmeek](https://github.com/judahmeek). ### [13.3.4] - 2023-05-23 #### Added + - Improved functionality of Filesystem-based pack generation & auto-bundling. Added `make_generated_server_bundle_the_entrypoint` configuration key. [PR 1531](https://github.com/shakacode/react_on_rails/pull/1531) by [judahmeek](https://github.com/judahmeek). #### Removed + - Removed unneeded `HMR=true` from `Procfile.dev` in install template [PR 1537](https://github.com/shakacode/react_on_rails/pull/1537) by [ahangarha](https://github.com/ahangarha). ### [13.3.3] - 2023-03-21 #### Fixed + - Fixed bug regarding loading FS-based packs. [PR 1527](https://github.com/shakacode/react_on_rails/pull/1527) by [judahmeek](https://github.com/judahmeek). ### [13.3.2] - 2023-02-24 #### Fixed + - Fixed the bug in `bin/dev` and `bin/dev-static` scripts by using `system` instead of `exec` and remove option to pass arguments [PR 1519](https://github.com/shakacode/react_on_rails/pull/1519) by [ahangarha](https://github.com/ahangarha). ### [13.3.1] - 2023-01-30 + #### Added + - Optimized `ReactOnRails::TestHelper`'s RSpec integration using `when_first_matching_example_defined`. [PR 1496](https://github.com/shakacode/react_on_rails/pull/1496) by [mcls](https://github.com/mcls). - + #### Fixed + - Fixed bug regarding FS-based packs generation. [PR 1515](https://github.com/shakacode/react_on_rails/pull/1515) by [pulkitkkr](https://github.com/pulkitkkr). ### [13.3.0] - 2023-01-29 + #### Fixed + - Fixed pack not found warning while using `react_component` and `react_component_hash` helpers, even when corresponding chunks are present. [PR 1511](https://github.com/shakacode/react_on_rails/pull/1511) by [pulkitkkr](https://github.com/pulkitkkr). -- Fixed FS-based packs generation functionality to trigger pack generation on the creation of a new react component inside `components_subdirectory`. [PR 1506](https://github.com/shakacode/react_on_rails/pull/1506) by [pulkitkkr](https://github.com/pulkitkkr). +- Fixed FS-based packs generation functionality to trigger pack generation on the creation of a new react component inside `components_subdirectory`. [PR 1506](https://github.com/shakacode/react_on_rails/pull/1506) by [pulkitkkr](https://github.com/pulkitkkr). - Upgrade several JS dependencies to fix security issues. [PR 1514](https://github.com/shakacode/react_on_rails/pull/1514) by [ahangarha](https://github.com/ahangarha). #### Added + - Added `./bin/dev` and `./bin/dev-static` executables to ease and standardize running the dev server. [PR 1491](https://github.com/shakacode/react_on_rails/pull/1491) by [ahangarha](https://github.com/ahangarha). ### [13.2.0] - 2022-12-23 - + #### Fixed + - Fix reactOnRailsPageUnloaded when there is no component on the page. Important for apps using both hotwire and react_on_rails. [PR 1498](https://github.com/shakacode/react_on_rails/pull/1498) by [NhanHo](https://github.com/NhanHo). - Fixing wrong type. The throwIfMissing param of getStore should be optional as it defaults to true. [PR 1480](https://github.com/shakacode/react_on_rails/pull/1480) by [wouldntsavezion](https://github.com/wouldntsavezion). #### Added + - Exposed `reactHydrateOrRender` utility via [PR 1481](https://github.com/shakacode/react_on_rails/pull/1481) by [vaukalak](https://github.com/vaukalak). ### [13.1.0] - 2022-08-20 #### Improved + - Removed addition of `mini_racer` gem by default. [PR 1453](https://github.com/shakacode/react_on_rails/pull/1453) by [vtamara](https://github.com/vtamara) and [tomdracz](https://github.com/tomdracz). Using `mini_racer` makes most sense when deploying or building in environments that do not have Javascript runtime present. Since `react_on_rails` requires Node.js, there's no reason to override `ExecJS` runtime with `mini_racer`. @@ -194,62 +240,80 @@ which was only used in the workaround. [PR 1580](https://github.com/shakacode/re - Added file-system-based automatic bundle generation feature. [PR 1455](https://github.com/shakacode/react_on_rails/pull/1455) by [pulkitkkr](https://github.com/pulkitkkr). #### Fixed + - Correctly unmount roots under React 18. [PR 1466](https://github.com/shakacode/react_on_rails/pull/1466) by [alexeyr](https://github.com/alexeyr). - Fixed the `You are importing hydrateRoot from "react-dom" [...] You should instead import it from "react-dom/client"` warning under React 18 ([#1441](https://github.com/shakacode/react_on_rails/issues/1441)). [PR 1460](https://github.com/shakacode/react_on_rails/pull/1460) by [alexeyr](https://github.com/alexeyr). In exchange, you may see a warning like this when building using any version of React below 18: + ``` WARNING in ./node_modules/react-on-rails/node_package/lib/reactHydrateOrRender.js19:25-52 Module not found: Error: Can't resolve 'react-dom/client' in '/home/runner/work/react_on_rails/react_on_rails/spec/dummy/node_modules/react-on-rails/node_package/lib' @ ./node_modules/react-on-rails/node_package/lib/ReactOnRails.js 34:45-78 @ ./client/app/packs/client-bundle.js 5:0-42 32:0-23 35:0-21 59:0-26 ``` + It can be safely [suppressed](https://webpack.js.org/configuration/other-options/#ignorewarnings) in your Webpack configuration. ### [13.0.2] - 2022-03-09 + #### Fixed + - React 16 doesn't support version property, causing problems loading React on Rails. [PR 1435](https://github.com/shakacode/react_on_rails/pull/1435) by [justin808](https://github.com/justin808). ### [13.0.1] - 2022-02-09 + #### Improved + - Updated the default generator. [PR 1431](https://github.com/shakacode/react_on_rails/pull/1431) by [justin808](https://github.com/justin808). ### [13.0.0] - 2022-02-08 + #### Breaking + - Removed webpacker as a dependency. Add gem Shakapacker to your project, and update your package.json to also use shakapacker. #### Fixed + - Proper throwing of exceptions. - Default configuration better handles test env. ### [12.6.0] - 2022-01-22 #### Added + - A `rendering_props_extension` configuration which takes a module with an `adjust_props_for_client_side_hydration` method, which is used to process props differently for server/client if `prerender` is set to `true`. [PR 1413](https://github.com/shakacode/react_on_rails/pull/1413) by [gscarv13](https://github.com/gscarv13) & [judahmeek](https://github.com/judahmeek). ### [12.5.2] - 2021-12-29 + #### Fixed + - Usage of config.build_production_command for custom command for production builds fixed. [PR 1415](https://github.com/shakacode/react_on_rails/pull/1415) by [judahmeek](https://github.com/judahmeek). ### [12.5.1] - 2021-12-27 #### Fixed + - A fatal server rendering error if running an ReactOnRails >=12.4.0 with ReactOnRails Pro <2.4.0. [PR 1412](https://github.com/shakacode/react_on_rails/pull/1412) by [judahmeek](https://github.com/judahmeek). ### [12.5.0] - 2021-12-26 #### Added + - Support for React 18, including the changed SSR API. [PR 1409](https://github.com/shakacode/react_on_rails/pull/1409) by [kylemellander](https://github.com/kylemellander). - Added webpack configuration files as part of the generator and updated webpacker to version 6. [PR 1404](https://github.com/shakacode/react_on_rails/pull/1404) by [gscarv13](https://github.com/gscarv13). - Supports Rails 7. #### Changed + - Changed logic of determining the usage of the default rails/webpacker webpack config or a custom command to only check if the config.build_production_command is defined. [PR 1402](https://github.com/shakacode/react_on_rails/pull/1402) by [justin808](https://github.com/justin808) and [gscarv13](https://github.com/gscarv13). - Minimum required Ruby is 2.7 to match latest rails/webpacker. ### [12.4.0] - 2021-09-22 + #### Added + - ScoutAPM tracing support for server rendering [PR 1379](https://github.com/shakacode/react_on_rails/pull/1379) by [justin808](https://github.com/justin808). - Ability to stop React on Rails from modifying or creating the `assets:precompile` task. [PR 1371](https://github.com/shakacode/react_on_rails/pull/1371) by [justin808](https://github.com/justin808). Thanks to [elstgav](https://github.com/elstgav) for [the suggestion](https://github.com/shakacode/react_on_rails/issues/1368)! @@ -257,30 +321,41 @@ which was only used in the workaround. [PR 1580](https://github.com/shakacode/re - Added the ability to have render functions return a promise to be awaited by React on Rails Pro Node Renderer. [PR 1380](https://github.com/shakacode/react_on_rails/pull/1380) by [judahmeek](https://github.com/judahmeek) ### [12.3.0] - 2021-07-26 + #### Added + - Ability to use with Turbo (@hotwired/turbo), as Turbolinks gets obsolete. [PR 1374](https://github.com/shakacode/react_on_rails/pull/1374) by [pgruener](https://github.com/pgruener) and [PR 1377](https://github.com/shakacode/react_on_rails/pull/1377) by [mdesantis](https://github.com/mdesantis). To configure turbo the following option can be set: `ReactOnRails.setOptions({ turbo: true })` ### [12.2.0] - 2021-03-25 + #### Added + - Ability to configure server react rendering to throw rather than just logging the error. Useful for React on Rails Pro Node rendering [PR 1365](https://github.com/shakacode/react_on_rails/pull/1365) by [justin808](https://github.com/justin808). ### [12.1.0] - 2021-03-23 + #### Added + - Added the ability to assign a module with a `call` method to `config.build_production_command`. See [the configuration docs](https://www.shakacode.com/react-on-rails/docs/guides/configuration). [PR 1362: Accept custom module for config.build_production_command](https://github.com/shakacode/react_on_rails/pull/1362). #### Fixed + - Stop setting NODE_ENV value during precompile, as it interfered with rails/webpacker's setting of NODE_ENV to production by default. Fixes [#1334](https://github.com/shakacode/react_on_rails/issues/1334). [PR 1356: Don't set NODE_ENV in assets.rake](https://github.com/shakacode/react_on_rails/pull/1356) by [alexrozanski](https://github.com/alexrozanski). ### [12.0.4] - 2020-11-14 + #### Fixed + - Install generator now specifies the version. Fixes [React on Rails Generator installs the older npm package #1336](https://github.com/shakacode/react_on_rails/issues/1336). [PR 1338: Fix Generator to use Exact NPM Version](https://github.com/shakacode/react_on_rails/pull/1338) by [justin808](https://github.com/justin808). ### [12.0.3] - 2020-09-20 + #### Fixed + - Async script loading optimizes page load speed. With this fix, a bundle can be loaded "async" and a handler function can determine when to hydrate. For an example of this, see the [docs for loadable-components SSR](https://loadable-components.com/docs/server-side-rendering/#4-add-loadableready-client-side). @@ -288,23 +363,30 @@ which was only used in the workaround. [PR 1580](https://github.com/shakacode/re Loadable-Components is supported by [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro). ### [12.0.2] - 2020-07-09 + #### Fixed + - Remove dependency upon Redux for Typescript types. [PR 1323](https://github.com/shakacode/react_on_rails/pull/1323) by [justin808](https://github.com/justin808). ### [12.0.1] - 2020-07-09 + #### Fixed + - Changed invocation of webpacker:clean to use a very large number of versions so it does not accidentally delete the server-bundle.js. [PR 1306](https://github.com/shakacode/react_on_rails/pull/1306) by By [justin808](https://github.com/justin808). ### [12.0.0] - 2020-07-08 + For upgrade instructions, see [docs/guides/upgrading-react-on-rails.md](https://www.shakacode.com/react-on-rails/docs/guides/upgrading-react-on-rails). #### Major Improvements + 1. **React Hooks Support** for top level components 2. **Typescript bindings** 3. **rails/webpacker** "just works" with React on Rails by default. 4. i18n support for generating a JSON file rather than a JS file. #### BREAKING CHANGE + In order to solve the issues regarding React Hooks compatibility, the number of parameters for functions is used to determine if you have a generator function that will get invoked to return a React component, or you are registering a functional React component. Alternately, you can @@ -316,19 +398,20 @@ See [docs/guides/upgrading-react-on-rails](https://www.shakacode.com/react-on-ra for details. #### Other Updates -* `react_on_rails` fully supports `rails/webpacker`. The example test app in `spec/dummy` was recently converted over to use rails/webpacker v4+. It's a good example of how to leverage rails/webpacker's webpack configuration for server-side rendering. -* Changed the precompile task to use the rails/webpacker one by default -* Updated generators to use React hooks -* Requires the use of rails/webpacker view helpers -* If the webpacker webpack config files exist, then React on Rails will not override the default + +- `react_on_rails` fully supports `rails/webpacker`. The example test app in `spec/dummy` was recently converted over to use rails/webpacker v4+. It's a good example of how to leverage rails/webpacker's webpack configuration for server-side rendering. +- Changed the precompile task to use the rails/webpacker one by default +- Updated generators to use React hooks +- Requires the use of rails/webpacker view helpers +- If the webpacker webpack config files exist, then React on Rails will not override the default assets:precompile setup by rails/webpacker. If you are not using the rails/webpacker setup for webpack, then be sure to remove the JS files inside of config/webpack, like `config/webpack/production.js.` -* Removed **env_javascript_include_tag** and **env_stylesheet_link_tag** as these are replaced by view helpers +- Removed **env_javascript_include_tag** and **env_stylesheet_link_tag** as these are replaced by view helpers from rails/webpacker -* Removal of support for old Rubies and Rails. -* Removal of config.symlink_non_digested_assets_regex as it's no longer needed with rails/webpacker. +- Removal of support for old Rubies and Rails. +- Removal of config.symlink_non_digested_assets_regex as it's no longer needed with rails/webpacker. If any business needs this, we can move the code to a separate gem. -* Added configuration option `same_bundle_for_client_and_server` with default `false` because +- Added configuration option `same_bundle_for_client_and_server` with default `false` because 1. Production applications would typically have a server bundle that differs from the client bundle 2. This change only affects trying to use HMR with react_on_rails with rails/webpacker. @@ -339,39 +422,48 @@ for details. If you are using the **same bundle for client and server rendering**, then set this configuration option to `true`. By [justin808](https://github.com/shakacode/react_on_rails/pull/1240). -* Added support to export locales in JSON format. New option added `i18n_output_format` which allows to +- Added support to export locales in JSON format. New option added `i18n_output_format` which allows to specify locales format either `JSON` or `JS`. **`JSON` format is now the default.** **Use this config setting to get the old behavior: config.i18n_output_format = 'js'** [PR 1271](https://github.com/shakacode/react_on_rails/pull/1271) by [ashgaliyev](https://github.com/ashgaliyev). -- Added Typescript definitions to the Node package. By [justin808](https://github.com/justin808) and [judahmeek](https://github.com/judahmeek) in [PR 1287](https://github.com/shakacode/react_on_rails/pull/1287). -- Removed restriction to keep the server bundle in the same directory with the client bundles. Rails/webpacker 4 has an advanced cleanup that will remove any files in the directory of other webpack files. Removing this restriction allows the server bundle to be created in a sibling directory. By [justin808](https://github.com/shakacode/react_on_rails/pull/1240). +* Added Typescript definitions to the Node package. By [justin808](https://github.com/justin808) and [judahmeek](https://github.com/judahmeek) in [PR 1287](https://github.com/shakacode/react_on_rails/pull/1287). +* Removed restriction to keep the server bundle in the same directory with the client bundles. Rails/webpacker 4 has an advanced cleanup that will remove any files in the directory of other webpack files. Removing this restriction allows the server bundle to be created in a sibling directory. By [justin808](https://github.com/shakacode/react_on_rails/pull/1240). ### [11.3.0] - 2019-05-24 + #### Added + - Added method for retrieving any option from `render_options` [PR 1213](https://github.com/shakacode/react_on_rails/pull/1213) -by [ashgaliyev](https://github.com/ashgaliyev). + by [ashgaliyev](https://github.com/ashgaliyev). - html_options has an option for 'tag' to set the html tag name like this: `html_options: { tag: "span" }`. -[PR 1208](https://github.com/shakacode/react_on_rails/pull/1208) by [tahsin352](https://github.com/tahsin352). + [PR 1208](https://github.com/shakacode/react_on_rails/pull/1208) by [tahsin352](https://github.com/tahsin352). ### [11.2.2] - 2018-12-24 + #### Improved + - rails_context can more easily be called from controller methods. The mandatory param of server_side has been made optional. ### [11.2.1] - 2018-12-06 + ## MIGRATION for v11.2 + - If using **React on Rails Pro**, upgrade react_on_rails_pro to a version >= 1.3. #### Improved + - To support React v16, updated API for manually calling `ReactOnRails.render(name, props, domNodeId, hydrate)`. Added 3rd @param hydrate Pass truthy to update server rendered html. Default is falsey Any truthy values calls hydrate rather than render. [PR 1159](https://github.com/shakacode/react_on_rails/pull/1159) by [justin808](https://github.com/justin808) and [coopersamuel](https://github.com/coopersamuel). - Enabled the use of webpack-dev-server with Server-side rendering. [PR 1173](https://github.com/shakacode/react_on_rails/pull/1173) by [justin808](https://github.com/justin808) and [judahmeek](https://github.com/judahmeek). #### Changed + - Changed the default for: + ```rb config.raise_on_prerender_error = Rails.env.development? ``` @@ -380,28 +472,37 @@ by [ashgaliyev](https://github.com/ashgaliyev). [PR 1145](https://github.com/shakacode/react_on_rails/pull/1145) by [justin808](https://github.com/justin808). ### 11.2.0 - 2018-12-06 + Do not use. Unpublished. Caused by an issue with the release script. ### [11.1.8] - 2018-10-14 #### Improved + - Improved tutorial and support for HMR when using `rails/webpacker` for Webpack configuration. [PR 1156](https://github.com/shakacode/react_on_rails/pull/1156) by [justin808](https://github.com/justin808). ### [11.1.7] - 2018-10-10 + #### Fixed + - Fixed bug where intl parsing would fail when trying to parse integers or blank entries. by [sepehr500](https://github.com/sepehr500) ### [11.1.6] - 2018-10-05 + #### Fixed + - Fix client startup invoking render prematurely, **AGAIN**. Fix additional cases of client startup failing during interactive readyState". Closes [issue #1150](https://github.com/shakacode/react_on_rails/issues/1150). [PR 1152](https://github.com/shakacode/react_on_rails/pull/1152) by [rakelley](https://github.com/rakelley). ### [11.1.5] - 2018-10-03 + #### Fixed -- Fix client startup invoking render prematurely. Closes [issue #1150](https://github.com/shakacode/react_on_rails/issues/1150). [PR 1151](https://github.com/shakacode/react_on_rails/pull/1151) by [rakelley](https://github.com/rakelley). + +- Fix client startup invoking render prematurely. Closes [issue #1150](https://github.com/shakacode/react_on_rails/issues/1150). [PR 1151](https://github.com/shakacode/react_on_rails/pull/1151) by [rakelley](https://github.com/rakelley). ### [11.1.4] - 2018-09-12 #### Fixed + - Ignore Arrays in Rails i18n yml files. [PR 1129](https://github.com/shakacode/react_on_rails/pull/1129) by [vcarel](https://github.com/vcarel). - Fix to apply transform-runtime. And work with Babel 6 and 7. (Include revert of [PR 1136](https://github.com/shakacode/react_on_rails/pull/1136)) [PR 1140](https://github.com/shakacode/react_on_rails/pull/1140) by [Ryunosuke Sato](https://github.com/tricknotes). - Upgrade Babel version to 7 [PR 1141](https://github.com/shakacode/react_on_rails/pull/1141) by [Ryunosuke Sato](https://github.com/tricknotes). @@ -409,62 +510,83 @@ Do not use. Unpublished. Caused by an issue with the release script. ### [11.1.3] - 2018-08-26 #### Fixed + - Don't apply babel-plugin-transform-runtime inside react-on-rails to work with babel 7. [PR 1136](https://github.com/shakacode/react_on_rails/pull/1136) by [Ryunosuke Sato](https://github.com/tricknotes). - Add support for webpacker 4 prereleases. [PR 1134](https://github.com/shakacode/react_on_rails/pull/1134) by [Judahmeek](https://github.com/Judahmeek)) ### [11.1.2] - 2018-08-18 #### Fixed + - Tests now properly exit if the config.build_test_command fails! - Source path for project using Webpacker would default to "app/javascript" even if when the node_modules directory was set to "client". Fix now makes the configuration of this crystal clear. - renamed method RenderOptions.has_random_dom_id? to RenderOptions.random_dom_id? for rubocop rule. -[PR 1133](https://github.com/shakacode/react_on_rails/pull/1133) by [justin808](https://github.com/justin808) + [PR 1133](https://github.com/shakacode/react_on_rails/pull/1133) by [justin808](https://github.com/justin808) ### [11.1.1] - 2018-08-09 + #### Fixed + - `TRUE` was deprecated in ruby 2.4, using `true` instead. [PR 1128](https://github.com/shakacode/react_on_rails/pull/1128) by [Aguardientico](https://github.com/Aguardientico). ### [11.1.0] - 2018-08-07 + #### Added + - Add random dom id option. This new global and react_component helper option allows configuring whether or not React on Rails will automatically add a random id to the DOM node ID. [PR 1121](https://github.com/shakacode/react_on_rails/pull/1121) by [justin808](https://github.com/justin808) - * Added configuration option random_dom_id - * Added method RenderOptions has_random_dom_id? + - Added configuration option random_dom_id + - Added method RenderOptions has_random_dom_id? + #### Fixed + - Fix invalid warn directive. [PR 1123](https://github.com/shakacode/react_on_rails/pull/1123) by [mustangostang](https://github.com/mustangostang). ### [11.0.10] - 2018-07-22 + #### Fixed + - Much better logging of rendering errors when there are lots of props. Only the a 1,000 chars are logged, and the center is indicated to be truncated. [PR 1117](https://github.com/shakacode/react_on_rails/pull/1117) and [PR 1118](https://github.com/shakacode/react_on_rails/pull/1118) by [justin808](https://github.com/justin808). - Properly clearing hydrated stores when server rendering. [PR 1120](https://github.com/shakacode/react_on_rails/pull/1120) by [squadette](https://github.com/squadette). ### [11.0.9] - 2018-06-24 -- Handle ``` @@ -167,11 +165,13 @@ Streaming SSR is particularly valuable in specific scenarios. Here's when to con ### Ideal Use Cases 1. **Data-Heavy Pages** + - Pages that fetch data from multiple sources - Dashboard-style layouts where different sections can load independently - Content that requires heavy processing or computation 2. **Progressive Enhancement** + - When you want users to see and interact with parts of the page while others load - For improving perceived performance on slower connections - When different parts of your page have different priority levels @@ -184,6 +184,7 @@ Streaming SSR is particularly valuable in specific scenarios. Here's when to con ### Best Practices for Streaming 1. **Component Structure** + ```jsx // Good: Independent sections that can stream separately diff --git a/docs/guides/tutorial.md b/docs/guides/tutorial.md index 6d95844e4..da4739cfc 100644 --- a/docs/guides/tutorial.md +++ b/docs/guides/tutorial.md @@ -1,10 +1,10 @@ # React on Rails Basic Tutorial -_Also see the example repo of [React on Rails Tutorial With SSR, HMR fast refresh, and TypeScript](https://github.com/shakacode/react_on_rails_demo_ssr_hmr)_ +_Also see the example repo of [React on Rails Tutorial With SSR, HMR fast refresh, and TypeScript](https://github.com/shakacode/react_on_rails_demo_ssr_hmr)_ ------ +--- -*Updated for Ruby 2.7, Rails 7, React on Rails v13, and Shakapacker v7* +_Updated for Ruby 2.7, Rails 7, React on Rails v13, and Shakapacker v7_ This tutorial guides you through setting up a new or existing Rails app with **React on Rails**, demonstrating Rails + React + Redux + Server Rendering. @@ -13,15 +13,17 @@ After finishing this tutorial you will get an application that can do the follow ![example](https://cloud.githubusercontent.com/assets/371302/17368567/111cc722-596b-11e6-9b72-ac5967a60e42.gif) You can find it here: -* [Source code for this app in PR, using the --redux option](https://github.com/shakacode/react_on_rails-test-new-redux-generation/pull/17) and [for Heroku](https://github.com/shakacode/react_on_rails-test-new-redux-generation/pull/18). -* [Live on Heroku](https://reactrails.com/) + +- [Source code for this app in PR, using the --redux option](https://github.com/shakacode/react_on_rails-test-new-redux-generation/pull/17) and [for Heroku](https://github.com/shakacode/react_on_rails-test-new-redux-generation/pull/18). +- [Live on Heroku](https://reactrails.com/) By the time you read this, the latest may have changed. Be sure to check the versions here: -* https://rubygems.org/gems/react_on_rails -* https://www.npmjs.com/package/react-on-rails +- +- # Table of Content: + - [Installation](#installation) - [Setting up your environment](#setting-up-your-environment) - [Create a new Ruby on Rails App](#create-a-new-ruby-on-rails-app) @@ -41,7 +43,9 @@ By the time you read this, the latest may have changed. Be sure to check the ver - [Custom IP & PORT setup (Cloud9 example)](#custom-ip--port-setup-cloud9-example) - [RubyMine performance tip](#rubymine-performance-tip) - [Conclusion](#conclusion) + # Installation + ## Setting up your environment Trying out **React on Rails** is super easy, so long as you have the basic prerequisites. @@ -53,6 +57,7 @@ Trying out **React on Rails** is super easy, so long as you have the basic prere - You need to have either [Overmind](https://github.com/DarthSim/overmind) or [Foreman](https://rubygems.org/gems/foreman) as a process manager. ## Create a new Ruby on Rails App + Then we need to create a fresh Rails application as follows. First, be sure to run `rails -v` and check you are using Rails 5.1.3 or above. If you are using an older version of Rails, you'll need to install webpacker with react per the instructions [here](https://github.com/rails/webpacker). @@ -66,9 +71,11 @@ rails new test-react-on-rails --skip-javascript cd test-react-on-rails ``` + Note: You can use `--database=postgresql` option to use Postgresql for the database. ## Add the Shakapacker and react_on_rails gems + We recommend using the latest version of these gems. Otherwise, specify the exact versions of both the gem and npm packages. In other words, don't use the `^` or `~` in the version specifications. @@ -143,7 +150,8 @@ React setup, will cause a full page refresh each time you save a file. # Deploying to Heroku ## Create Your Heroku App -*Assuming you can log in to heroku.com and have logged into your shell for Heroku.* + +_Assuming you can log in to heroku.com and have logged into your shell for Heroku._ 1. Visit [https://dashboard.heroku.com/new](https://dashboard.heroku.com/new) and create an app, say named `my-name-react-on-rails`: @@ -163,6 +171,7 @@ heroku buildpacks:add --index 1 heroku/nodejs ``` ## Swap out sqlite for postgres: + Heroku requires your app to use Postgresql. If you have not setup your app with Postgresql, you need to change your app settings to use this database. @@ -234,6 +243,7 @@ web: bundle exec puma -C config/puma.rb Note, newer versions of Rails create this file automatically. However, the [docs on Heroku](https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#config) have something a bit different, so please make it conform to those docs. As of 2020-06-04, the docs looked like this: `config/puma.rb` + ```rb workers Integer(ENV['WEB_CONCURRENCY'] || 2) threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5) @@ -278,6 +288,7 @@ heroku open and you will see your live app and you can share this URL with your friends. Congrats! # Other features + ## Turning on Server Rendering You can turn on server rendering by simply changing the `prerender` option to `true`: @@ -305,19 +316,27 @@ When you look at the source code for the page (right click, view source in Chrom versus with server rendering: ```html -

Hello, Stranger!


+
+
+

+ Hello, + Stranger! +

+
+
+
+
``` For more details on server rendering, see: - + [Client vs. Server Rendering](https://www.shakacode.com/react-on-rails/docs/guides/client-vs-server-rendering/) - + [React Server Rendering](https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/) +- [Client vs. Server Rendering](https://www.shakacode.com/react-on-rails/docs/guides/client-vs-server-rendering/) +- [React Server Rendering](https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/) ## Moving from the Rails default `/app/javascript` to the recommended `/client` structure ShakaCode recommends that you use `/client` for your client side app. This way a non-Rails, front-end developer can be at home just by opening up the `/client` directory. - 1. Move the directory: ```bash @@ -338,18 +357,17 @@ When you change and save a JSX file, the browser will automatically refresh! So you get some basics from HMR with no code changes. If you want to go further, take a look at these links: -* [webpack-dev-server](https://github.com/rails/webpacker/blob/5-x-stable/docs/webpack-dev-server.md) -* [DevServer](https://webpack.js.org/configuration/dev-server/) -* [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) +- [webpack-dev-server](https://github.com/rails/webpacker/blob/5-x-stable/docs/webpack-dev-server.md) +- [DevServer](https://webpack.js.org/configuration/dev-server/) +- [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) React on Rails will automatically handle disabling server rendering if there is only one bundle file created by the Webpack development server by `shakapcker`. - ## Custom IP & PORT setup (Cloud9 example) In case you are running some custom setup with different IP or PORT you should also edit Procfile.dev. For example, to be able to run on free Cloud9 IDE we are putting IP 0.0.0.0 and PORT 8080. The default generated file `Procfile.dev` uses `-p 3000`. -``` Procfile.dev +```Procfile.dev web: rails s -p 8080 -b 0.0.0.0 ``` @@ -359,12 +377,12 @@ Then visit https://your-shared-addr.c9users.io:8080/hello_world It's super important to exclude certain directories from RubyMine or else it will slow to a crawl as it tries to parse all the npm files. -* Generated files, per the settings in your `config/shakapacker.yml`, which default to `public/packs` and `public/packs-test` -* `node_modules` +- Generated files, per the settings in your `config/shakapacker.yml`, which default to `public/packs` and `public/packs-test` +- `node_modules` # Conclusion -* Browse the docs on [our documentation website](https://www.shakacode.com/react-on-rails/docs/) +- Browse the docs on [our documentation website](https://www.shakacode.com/react-on-rails/docs/) Feedback is greatly appreciated! As are stars on github! diff --git a/docs/guides/upgrading-react-on-rails.md b/docs/guides/upgrading-react-on-rails.md index 4700c37b2..029cc7624 100644 --- a/docs/guides/upgrading-react-on-rails.md +++ b/docs/guides/upgrading-react-on-rails.md @@ -1,6 +1,7 @@ # Upgrading React on Rails ## Need Help Migrating? + If you would like help in migrating between React on Rails versions or help with implementing server rendering, please contact [justin@shakacode.com](mailto:justin@shakacode.com) for more information about our [React on Rails Pro Support](https://www.shakacode.com/react-on-rails-pro). We specialize in helping companies to quickly and efficiently upgrade. The older versions use the Rails asset pipeline to package client assets. The current and recommended way is to use Webpack 4+ for asset preparation. You may also need help migrating from the `rails/webpacker`'s Webpack configuration to a better setup ready for Server Side Rendering. @@ -8,6 +9,7 @@ We specialize in helping companies to quickly and efficiently upgrade. The older ## Upgrading to v13 ### Breaking Change + Previously, the gem `webpacker` was a Gem dependency. v13 has changed slightly to switch to `shakapacker`. @@ -21,17 +23,21 @@ In summary: 3. Other updates, depending on what version of `rails/webpacker` that you had. ## Upgrading to v12 + ### Recent versions + Make sure that you are on a relatively more recent version of rails and webpacker. Yes, the [rails/webpacker](https://github.com/rails/webpacker) gem is required! v12 is tested on Rails 6. It should work on Rails v5. If you're on any older version, and v12 doesn't work, please file an issue. ### Removed Configuration config.symlink_non_digested_assets_regex + Remove `config.symlink_non_digested_assets_regex` from your `config/initializers/react_on_rails.rb`. If you still need that feature, please file an issue. ### i18n default format changed to JSON -* If you're using the internalization helper, then set `config.i18n_output_format = 'js'`. You can + +- If you're using the internalization helper, then set `config.i18n_output_format = 'js'`. You can later update to the default JSON format as you will need to update your usage of that file. A JSON format is more efficient. @@ -46,36 +52,42 @@ more information on what a Render-Function is. ##### Update required for registered functions taking exactly 2 params. Registered Objects are of the following type: -1. **Function that takes only zero or one params and you return a React Element**, often JSX. If the function takes zero or one params, there is **no migration needed** for that function. - ```js - export default (props) => ; - ``` -2. **Function that takes only zero or one params and you return an Object (_not a React Element_)**. If the function takes zero or one params, **you need to add one or two unused params so you have exactly 2 params** and then that function will be treated as a render function and it can return an Object rather than a React element. If you don't do this, you'll see this obscure error message: +1. **Function that takes only zero or one params and returns a React Element**, often JSX. If the function takes zero or one params, there is **no migration needed** for that function. + + ```js + export default (props) => ; + ``` + +2. **Function that takes only zero or one params and returns an Object (_not a React Element_)**. If the function takes zero or one params, **you need to add one or two unused params so you have exactly 2 params** and then that function will be treated as a render function and it can return an Object rather than a React element. If you don't do this, you'll see this obscure error message: ``` [SERVER] message: Objects are not valid as a React child (found: object with keys {renderedHtml}). If you meant to render a collection of children, use an array instead. in YourComponentRenderFunction ``` - So look in YourComponentRenderFunction and do this change +So look in `YourComponentRenderFunction` and do this change ```js - export default (props) => { renderedHTML: getRenderedHTML }; +export default (props) => ({ + renderedHTML: getRenderedHTML(), +}); ``` - To have exactly 2 arguments: +To have exactly 2 arguments: ```js - export default (props, _railsContext) => { renderedHTML: getRenderedHTML }; -``` +export default (props, _railsContext) => ({ + renderedHTML: getRenderedHTML(), +}); +``` 3. Function that takes **2 params** and returns **a React function or class component**. _Migration is needed as the older syntax returned a React Element._ A function component is a function that takes zero or one params and returns a React Element, like JSX. The correct syntax looks like: - ```js - export default (props, railsContext) => () => ; - ``` + ```js + export default (props, railsContext) => () => ; + ``` Note, you cannot return a React Element (JSX). See below for the migration steps. If your function that took **two params returned an Object**, then no migration is required. 4. Function that takes **3 params** and uses the 3rd param, `domNodeId`, to call `ReactDOM.hydrate`. If the function takes 3 params, there is **no migration needed** for that function. @@ -88,8 +100,9 @@ The fix is simple. Here is an example of the change you'll do: ![2020-07-07_09-43-51 (1)](https://user-images.githubusercontent.com/1118459/86927351-eff79e80-c0ce-11ea-9172-d6855c45e2bb.png) ##### Broken, as this function takes two params and it returns a React Element from a JSX Literal + ```js -export default (props, railsContext) => ; +export default (props, _railsContext) => ; ``` If you make this mistake, you'll get this warning @@ -111,56 +124,64 @@ wrapper such that you're returning a function rather than a React Element, then: 1. You won't see anything render. 2. You will see this warning in development mode: `Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.` ---------- +--- ## Upgrading rails/webpacker from v3 to v4 + ### Custom Webpack build file + The default value for `extract_css` is **false** in `config/webpack.yml`. Custom webpack builds should set this value to true or else no CSS link tags are generated. You have a custom webpack build if you are not using [rails/webpacker](https://github.com/rails/webpacker) to setup your Webpack configuration. - ```yml - default: &default - # other stuff - extract_css: true - # by default, extract and emit a css file. The default is false - ``` +```yml +default: &default # other stuff + extract_css: true + # by default, extract and emit a css file. The default is false +``` ## Upgrading to version 11 -* Remove `server_render_method` from config/initializers/react_on_rails.rb. Alternate server rendering methods are part of React on Rails Pro. If you want to use a custom renderer, contact justin@shakacode.com. We have a custom node rendering solution in production for egghead.io. -* Remove your usage of ENV["TRACE_REACT_ON_RAILS"] usage. You can get all tracing with either specifying **`trace`** at your component or in your config/initializers/react_on_rails.rb file. -* ReactOnRails::Utils.server_bundle_file_name and ReactOnRails::Utils.bundle_file_name were removed. React on Rails Pro contains upgrades to enable component and other types caching with React on Rails. + +- Remove `server_render_method` from config/initializers/react_on_rails.rb. Alternate server rendering methods are part of React on Rails Pro. If you want to use a custom renderer, contact justin@shakacode.com. We have a custom node rendering solution in production for egghead.io. +- Remove your usage of ENV["TRACE_REACT_ON_RAILS"] usage. You can get all tracing with either specifying **`trace`** at your component or in your config/initializers/react_on_rails.rb file. +- ReactOnRails::Utils.server_bundle_file_name and ReactOnRails::Utils.bundle_file_name were removed. React on Rails Pro contains upgrades to enable component and other types caching with React on Rails. ## Upgrading to version 10 Pretty simple: -* Follow the steps to migrate to version 9 (except installing 10.x instead of 9.x) -* If you have `react_component` returning hashes, then switch to `react_component_hash` instead + +- Follow the steps to migrate to version 9 (except installing 10.x instead of 9.x) +- If you have `react_component` returning hashes, then switch to `react_component_hash` instead ## Upgrading to version 9 ### Why Webpacker? + Webpacker provides areas of value: -* View helpers that support bypassing the asset pipeline, which allows you to avoid double minification and enable source maps in production. This is 100% a best practice as source maps in production greatly increases the value of services such as HoneyBadger or Sentry. -* A default Webpack config so that you only need to do minimal modifications and customizations. However, if you're doing server rendering, you may not want to give up control. Since Webpacker's default webpack config is changing often, we at Shakacode can give you definitive advice on webpack configuration best practices. In general, if you're happy with doing your own Webpack configuration, then we suggest using the `client` strategy discussed below. Most corporate projects will prefer having more control than direct dependence on webpacker easily allows. + +- View helpers that support bypassing the asset pipeline, which allows you to avoid double minification and enable source maps in production. This is 100% a best practice as source maps in production greatly increases the value of services such as HoneyBadger or Sentry. +- A default Webpack config so that you only need to do minimal modifications and customizations. However, if you're doing server rendering, you may not want to give up control. Since Webpacker's default webpack config is changing often, we at Shakacode can give you definitive advice on webpack configuration best practices. In general, if you're happy with doing your own Webpack configuration, then we suggest using the `client` strategy discussed below. Most corporate projects will prefer having more control than direct dependence on webpacker easily allows. ### Integrating Webpacker + Reason for doing this: This enables your webpack bundles to bypass the Rails asset pipeline and it's extra minification, enabling you to use source-maps in production, while still maintaining total control over everything in the client directory #### From version 7 or lower ##### ...while keeping your `client` directory -* `.gitignore`: add `/public/webpack/*` -* `Gemfile`: bump `react_on_rails` and add `webpacker` -* layout views: anything bundled by webpack will need to be requested by a `javascript_pack_tag` or `stylesheet_pack_tag`. - * Search your codebase for javascript_include_tag. Use the -* `config/initializers/assets.rb`: we no longer need to modify `Rails.application.config.assets.paths` or append anything to `Rails.application.config.assets.precompile`. -* `config/initializers/react_on_rails.rb`: - * Delete `config.generated_assets_dir`. Webpacker's config now supplies this information - * Replace `config.npm_build_(test|production)_command` with `config.build_(test|production)_command` -* `config/webpacker.yml`: start with our [example config](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/config/webpacker.yml) (feel free to modify it as needed). I recommend setting dev_server.hmr to false however since HMR is currently broken. -* `client/package.json`: bump `react_on_rails` (I recommend bumping `webpack` as well). You'll also need `js-yaml` if you're not already using `eslint` and `webpack-manifest-plugin` regardless. - -###### Client Webpack config: - * You'll need the following code to read data from the webpacker config: + +- `.gitignore`: add `/public/webpack/*` +- `Gemfile`: bump `react_on_rails` and add `webpacker` +- layout views: anything bundled by webpack will need to be requested by a `javascript_pack_tag` or `stylesheet_pack_tag`. +- Search your codebase for javascript_include_tag. Use the +- `config/initializers/assets.rb`: we no longer need to modify `Rails.application.config.assets.paths` or append anything to `Rails.application.config.assets.precompile`. +- `config/initializers/react_on_rails.rb`: + - Delete `config.generated_assets_dir`. Webpacker's config now supplies this information + - Replace `config.npm_build_(test|production)_command` with `config.build_(test|production)_command` +- `config/webpacker.yml`: start with our [example config](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/config/webpacker.yml) (feel free to modify it as needed). I recommend setting dev_server.hmr to false however since HMR is currently broken. +- `client/package.json`: bump `react_on_rails` (I recommend bumping `webpack` as well). You'll also need `js-yaml` if you're not already using `eslint` and `webpack-manifest-plugin` regardless. + +###### Client Webpack config: + +- You'll need the following code to read data from the webpacker config: ``` const path = require('path'); @@ -171,7 +192,7 @@ const configPath = path.resolve('..', 'config'); const { output } = webpackConfigLoader(configPath); ``` - * That output variable will be used for webpack's `output` rules: +- That output variable will be used for webpack's `output` rules: ``` output: { @@ -181,7 +202,7 @@ const { output } = webpackConfigLoader(configPath); }, ``` - * ...as well as for the output of plugins like `webpack-manifest-plugin`: +- ...as well as for the output of plugins like `webpack-manifest-plugin`: ``` @@ -191,23 +212,24 @@ const { output } = webpackConfigLoader(configPath); }), ``` - * If you're using referencing files or images with `url-loader` & `file-loader`, their publicpaths will have to change as well: `publicPath: '/webpack/',` - * If you're using `css-loader`, `webpack.optimize.CommonsChunkPlugin`, or `extract-text-webpack-plugin`, they will also need cache-busting! +- If you're using referencing files or images with `url-loader` & `file-loader`, their publicpaths will have to change as well: `publicPath: '/webpack/',` +- If you're using `css-loader`, `webpack.optimize.CommonsChunkPlugin`, or `extract-text-webpack-plugin`, they will also need cache-busting! ...and you're finally done! ##### ...while replacing your `client` directory -* Make the same changes to `config/initializers/react_on_rails.rb as described above` -* Upgrade RoR & add Webpacker in the Gemfile -* Upgrade RoR in the `client/package.json` -* Run `bundle` -* Run `rails webpacker:install` -* Run `rails webpacker:install:react` -* Run `rails g react_on_rails:install` -* Move your entry point files to `app/javascript/packs` -* Either: - * Move all your source code to `app/javascript/bundles`, move your linter configs to the root directory, and then delete the `client` directory - * or just delete the webpack config and remove webpack, its loaders, and plugins from your `client/package.json`. + +- Make the same changes to `config/initializers/react_on_rails.rb as described above` +- Upgrade RoR & add Webpacker in the Gemfile +- Upgrade RoR in the `client/package.json` +- Run `bundle` +- Run `rails webpacker:install` +- Run `rails webpacker:install:react` +- Run `rails g react_on_rails:install` +- Move your entry point files to `app/javascript/packs` +- Either: + - Move all your source code to `app/javascript/bundles`, move your linter configs to the root directory, and then delete the `client` directory + - or just delete the webpack config and remove webpack, its loaders, and plugins from your `client/package.json`. ...and you're done. @@ -216,10 +238,12 @@ const { output } = webpackConfigLoader(configPath); For an example of upgrading, see [react-webpack-rails-tutorial/pull/416](https://github.com/shakacode/react-webpack-rails-tutorial/pull/416). - Breaking Configuration Changes - 1. Added `config.node_modules_location` which defaults to `""` if Webpacker is installed. You may want to set this to 'client'` to `config/initializers/react_on_rails.rb` to keep your node_modules inside of `/client` + + 1. Added `config.node_modules_location` which defaults to `""` if Webpacker is installed. You may want to set this to 'client'`to`config/initializers/react_on_rails.rb`to keep your node_modules inside of`/client` 2. Renamed - * config.npm_build_test_command ==> config.build_test_command - * config.npm_build_production_command ==> config.build_production_command + + - config.npm_build_test_command ==> config.build_test_command + - config.npm_build_production_command ==> config.build_production_command - Update the gemfile. Switch over to using the webpacker gem. @@ -229,18 +253,21 @@ gem "webpacker" - Update for the renaming in the `WebpackConfigLoader` in your webpack configuration. You will need to rename the following object properties: - - webpackOutputPath ==> output.path + + - webpackOutputPath ==> output.path - webpackPublicOutputDir ==> output.publicPath - - hotReloadingUrl ==> output.publicPathWithHost - - hotReloadingHostname ==> settings.dev_server.host - - hotReloadingPort ==> settings.dev_server.port - - hmr ==> settings.dev_server.hmr - - manifest ==> Remove this one. We use the default for Webpack of manifest.json - - env ==> Use `const { env } = require('process');` - - devBuild ==> Use `const devBuild = process.env.NODE_ENV !== 'production';` + - hotReloadingUrl ==> output.publicPathWithHost + - hotReloadingHostname ==> settings.dev_server.host + - hotReloadingPort ==> settings.dev_server.port + - hmr ==> settings.dev_server.hmr + - manifest ==> Remove this one. We use the default for Webpack of manifest.json + - env ==> Use `const { env } = require('process');` + - devBuild ==> Use `const devBuild = process.env.NODE_ENV !== 'production';` - Edit your Webpack.config files: + - Change your Webpack output to be like this. **Be sure to have the hash or chunkhash in the filename,** unless the bundle is server side.: + ``` const webpackConfigLoader = require('react-on-rails/webpackConfigLoader'); const configPath = resolve('..', 'config'); @@ -256,7 +283,9 @@ gem "webpacker" path: output.path, }, ``` + - Change your ManifestPlugin definition to something like the following + ``` new ManifestPlugin({ publicPath: output.publicPath, @@ -266,6 +295,7 @@ gem "webpacker" ``` - Find your `webpacker_lite.yml` and rename it to `webpacker.yml` + - Consider copying a default webpacker.yml setup such as https://github.com/shakacode/react-on-rails-v9-rc-generator/blob/master/config/webpacker.yml - If you are not using the webpacker webpacker setup, be sure to put in `compile: false` in the `default` section. - Alternately, if you are updating from webpacker_lite, you can manually change these: @@ -273,7 +303,7 @@ gem "webpacker" ``` cache_manifest: false ``` - - For production, set: + - For production, set: ``` cache_manifest: true ``` @@ -295,10 +325,11 @@ gem "webpacker" - Set the `hmr` key in your `webpacker.yml` to `true`. ### Without integrating webpacker -* Bump your ReactOnRails versions in `Gemfile` & `package.json` -* In `/config/initializers/react_on_rails.rb`: - * Rename `config.npm_build_test_command` ==> `config.build_test_command` - * Rename `config.npm_build_production_command` ==> `config.build_production_command` - * Add `config.node_modules_location = "client"` + +- Bump your ReactOnRails versions in `Gemfile` & `package.json` +- In `/config/initializers/react_on_rails.rb`: + - Rename `config.npm_build_test_command` ==> `config.build_test_command` + - Rename `config.npm_build_production_command` ==> `config.build_production_command` + - Add `config.node_modules_location = "client"` ...and you're done. diff --git a/docs/guides/webpack-configuration.md b/docs/guides/webpack-configuration.md index 0513f0b9d..3a14e713e 100644 --- a/docs/guides/webpack-configuration.md +++ b/docs/guides/webpack-configuration.md @@ -24,7 +24,7 @@ A key decision in your use React on Rails is whether you go with the Shakapacker Typical Shakapacker apps have a standard directory structure as documented [here](https://github.com/shakacode/shakapacker/blob/master/README.md#configuration-and-code). If you follow the steps in the the [basic tutorial](https://www.shakacode.com/react-on-rails/docs/guides/tutorial/), you will see this pattern in action. In order to customize the Webpack configuration, you need to consult with the [webpack configuration](https://www.shakacode.com/react-on-rails/docs/javascript/webpack/). -The *advantage* of using Shakapacker to configure Webpack is that there is very little code needed to get started and you don't need to understand really anything about webpack customization. +The _advantage_ of using Shakapacker to configure Webpack is that there is very little code needed to get started, and you don't need to understand really anything about webpack customization. ## Option 2: Traditional React on Rails using the /client directory @@ -38,5 +38,5 @@ const { config, devServer } = require('shakapacker'); You will want to consider using some of the same values set in these files: -* https://github.com/shakacode/shakapacker/blob/master/package/environments/base.js -* https://github.com/shakacode/shakapacker/blob/master/package/environments/development.js +- https://github.com/shakacode/shakapacker/blob/master/package/environments/base.js +- https://github.com/shakacode/shakapacker/blob/master/package/environments/development.js diff --git a/docs/home.md b/docs/home.md index 469dc59f4..786aa10e0 100644 --- a/docs/home.md +++ b/docs/home.md @@ -1,6 +1,7 @@ # React on Rails ## Details + 1. [Overview](https://www.shakacode.com/react-on-rails/docs/guides/react-on-rails-overview/) 1. [Getting Started](https://www.shakacode.com/react-on-rails/docs/getting-started/) 1. [How React on Rails Works](https://www.shakacode.com/react-on-rails/docs/guides/how-react-on-rails-works/) @@ -10,14 +11,17 @@ 1. [Deployment](https://www.shakacode.com/react-on-rails/docs/guides/deployment/). ## Changes and Upgrades + 1. [CHANGELOG.md](https://github.com/shakacode/react_on_rails/tree/master/CHANGELOG.md) 2. [Upgrading React on Rails](https://www.shakacode.com/react-on-rails/docs/guides/upgrading-react-on-rails/#upgrading-to-v12). ## Example Apps + 1. [spec/dummy](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy) example repo for a simple configuration of webpack via the `shakacode/shakapacker` gem -that supports SSR. + that supports SSR. 2. Example repo of [React on Rails Tutorial With SSR, HMR fast refresh, and TypeScript](https://github.com/shakacode/react_on_rails_demo_ssr_hmr) for a new way to setup the creation of your SSR bundle with `shakacode/shakapacker`. 3. Live, [open source](https://github.com/shakacode/react-webpack-rails-tutorial), example of this gem, see [reactrails.com](http://reactrails.com). # Other Resources + 1. RailsConf 2020 talk: [Webpacker, It-Just-Works, But How?](https://www.shakacode.com/blog/railsconf-2020-webpacker-it-just-works-but-how/) diff --git a/docs/javascript/angular-js-integration-migration.md b/docs/javascript/angular-js-integration-migration.md index af1a58ad3..23ac7524e 100644 --- a/docs/javascript/angular-js-integration-migration.md +++ b/docs/javascript/angular-js-integration-migration.md @@ -3,9 +3,11 @@ [React on Rails](https://github.com/shakacode/react_on_rails) offers a smooth transition to migrating your existing [AngularJS](https://angularjs.org/) + Rails application to use React with Webpack on top of Rails. Here are a few highlights and tips. ## Assets Handling -Ideally, you should have your JavaScript libraries packaged by `webpack` and gathered by `yarn`. If you have not already done this, then you can setup the `ReactOnRails` default JS code directory of `/client` to load the JS libraries related to AngularJS, etc. You can configure Webpack to globally export these libraries, so inclusion this way will be no different than using the Rails asset pipeline. However, so long as you *understand* how your JavaScript will eventually make its way onto your main layout, you will be OK. + +Ideally, you should have your JavaScript libraries packaged by `webpack` and gathered by `yarn`. If you have not already done this, then you can setup the `ReactOnRails` default JS code directory of `/client` to load the JS libraries related to AngularJS, etc. You can configure Webpack to globally export these libraries, so inclusion this way will be no different than using the Rails asset pipeline. However, so long as you _understand_ how your JavaScript will eventually make its way onto your main layout, you will be OK. ## Styling and CSS Modules + Once you move to Webpack, you can start using CSS modules. However, you'll need to carefully consider if your styling needs to apply to legacy AngularJS components in your app. ## ngReact Package @@ -22,7 +24,7 @@ We love using [StoryBook](https://getstorybook.io/) to create a simple testing a ## Overall Approach? -The big question when doing the migration from AngularJS to React is whether you should replace leaf level components first, to minimize the changes before you can deploy your hybrid AngularJS and React app. The alternative is to try to replace larger chunks at once. Both approaches have pros and cons. +The big question when doing the migration from AngularJS to React is whether you should replace leaf level components first, to minimize the changes before you can deploy your hybrid AngularJS and React app. The alternative is to try to replace larger chunks at once. Both approaches have pros and cons. 1. Frequent deploys with incremental parts of AngularJS replaced by React allows smaller incremental deploys and easier regression analysis should something break. On the negative side, any ping-pong of data between AngularJS and React can result in a complicated and convoluted architecture. 2. Larger deploys of a full screen can yield efficiencies such as converting the whole screen to use one Redux store. However, this can be a large chunk of code to test and deploy. diff --git a/docs/javascript/capistrano-deployment.md b/docs/javascript/capistrano-deployment.md index 80f9c8e79..8b1c1366f 100644 --- a/docs/javascript/capistrano-deployment.md +++ b/docs/javascript/capistrano-deployment.md @@ -1,18 +1,25 @@ # Capistrano Deployment -Make sure ReactOnRails is working in development environment. + +Make sure ReactOnRails is working in the development environment. Add the following to development your Gemfile and bundle install. -``` ruby + +```ruby group :development do gem 'capistrano-yarn' end ``` + Then run Bundler to ensure Capistrano is downloaded and installed. -``` sh + +```sh $ bundle install ``` + Add the following in your Capfile. -``` ruby + +```ruby require 'capistrano/yarn' ``` -If the deployment is taking too long or getting stuck at assets:precompile stage, it probably is because of memory. Webpack consumes a lot of memory so if possible, try increasing the RAM of your server. + +If the deployment is taking too long or getting stuck at `assets:precompile` stage, it is probably because of memory. Webpack consumes a lot of memory so if possible, try increasing the RAM of your server. diff --git a/docs/javascript/code-splitting.md b/docs/javascript/code-splitting.md index 8eb2733f2..f2056d823 100644 --- a/docs/javascript/code-splitting.md +++ b/docs/javascript/code-splitting.md @@ -4,7 +4,7 @@ _Note: This document is outdated._ Please email [justin@shakacode.com](mailto:ju if you would be interested in help with code splitting using [loadable-components.com](https://loadable-components.com/docs) with React on Rails. ------ +--- What is code splitting? From the webpack documentation: @@ -29,6 +29,7 @@ To prevent this, you have to wait until the code chunk is fetched before doing t Here's an example of how you might use this in practice: #### page.html.erb + ```erb <%= react_component("NavigationApp", prerender: true) %> <%= react_component("RouterApp", prerender: true) %> @@ -36,6 +37,7 @@ Here's an example of how you might use this in practice: ``` #### clientRegistration.js + ```js import ReactOnRails from 'node_package/lib/ReactOnRails'; import NavigationApp from './NavigationApp'; @@ -45,7 +47,7 @@ import NavigationApp from './NavigationApp'; import RouterApp from './RouterAppRenderer'; import applicationStore from '../store/applicationStore'; -ReactOnRails.registerStore({applicationStore}); +ReactOnRails.registerStore({ applicationStore }); ReactOnRails.register({ NavigationApp, RouterApp, @@ -53,6 +55,7 @@ ReactOnRails.register({ ``` #### serverRegistration.js + ```js import ReactOnRails from 'react-on-rails'; import NavigationApp from './NavigationApp'; @@ -61,15 +64,17 @@ import NavigationApp from './NavigationApp'; import RouterApp from './RouterAppServer'; import applicationStore from '../store/applicationStore'; -ReactOnRails.registerStore({applicationStore}); +ReactOnRails.registerStore({ applicationStore }); ReactOnRails.register({ NavigationApp, RouterApp, }); ``` + Note that you should not register a renderer on the server, since there won't be a domNodeId when we're server rendering. Note that the `RouterApp` imported by `serverRegistration.js` is from a different file. For an example of how to set up an app for server rendering, see the [react router docs](https://www.shakacode.com/react-on-rails/docs/javascript/react-router/). #### RouterAppRenderer.jsx + ```jsx import ReactOnRails from 'react-on-rails'; import React from 'react'; @@ -143,7 +148,7 @@ Add the following to the output key of your webpack config: config = { output: { publicPath: '/assets/', - } + }, }; ``` diff --git a/docs/javascript/converting-from-custom-webpack-config-to-rails-webpacker-config.md b/docs/javascript/converting-from-custom-webpack-config-to-rails-webpacker-config.md index 8a56160a1..2ec150ae6 100644 --- a/docs/javascript/converting-from-custom-webpack-config-to-rails-webpacker-config.md +++ b/docs/javascript/converting-from-custom-webpack-config-to-rails-webpacker-config.md @@ -1,10 +1,10 @@ # Converting from Custom Webpack Config to Rails Shakapacker Config 1. Compare your package.json and the dependencies in https://github.com/shakacode/shakapacker/blob/master/package.json - and avoid any duplicates. We don't want different versions of the same packages. - We want the versions from `shakacode/shakapacker` unless we specifically want to override them. + and avoid any duplicates. We don't want different versions of the same packages. + We want the versions from `shakacode/shakapacker` unless we specifically want to override them. 2. Search the `shakacode/shakapacker` repo for anything you're not sure about in terms of package names. 3. Run `bin/shakapacker` and make sure there are zero errors 4. Update webpack plugins and loaders to current or close to current 5. Make sure that your `bin/shakapacker` and `bin/shakapacker` match the latest on -https://github.com/shakacode/shakapacker/tree/master/lib/install/bin + https://github.com/shakacode/shakapacker/tree/master/lib/install/bin diff --git a/docs/javascript/credits.md b/docs/javascript/credits.md index b265a3d09..8c4091648 100644 --- a/docs/javascript/credits.md +++ b/docs/javascript/credits.md @@ -7,4 +7,3 @@ The origins of the project began with the need to do a rich JavaScript interface The gem project started with [Justin Gordon](https://github.com/justin808/) pairing with [Samnang Chhun](https://github.com/samnang) to figure out how to do server rendering with Webpack plus Rails. [Alex Fedoseev](https://github.com/alexfedoseev) then joined in. [Rob Wise](https://github.com/robwise), [Aaron Van Bokhoven](https://github.com/aaronvb), and [Andy Wang](https://github.com/yorzi) did the bulk of the generators. Many others have [contributed](https://github.com/shakacode/react_on_rails/graphs/contributors). The gem was initially inspired by the [react-rails gem](https://github.com/reactjs/react-rails). - diff --git a/docs/javascript/foreman-issues.md b/docs/javascript/foreman-issues.md index b64526e66..c2365147a 100644 --- a/docs/javascript/foreman-issues.md +++ b/docs/javascript/foreman-issues.md @@ -8,8 +8,8 @@ See: https://github.com/ddollar/foreman ## Known issues - * With `foreman 0.82.0` npm `react-s3-uploader` was failing to finish upload file to S3 when server was started by `foreman -f Procfile.dev`, - at the same time the same code works fine when ruby server started by `bundle exec rails s`. +- With `foreman 0.82.0` npm `react-s3-uploader` was failing to finish upload file to S3 when server was started by `foreman -f Procfile.dev`, + at the same time the same code works fine when the Ruby server is started by `bundle exec rails s`. - * The same Procfile with different versions of `foreman` in combination with different versions of `bundler` may produce different output of `ps aux`. - This may brake bash tools which rely on `ps` output. +- The same Procfile with different versions of `foreman` in combination with different versions of `bundler` may produce different output of `ps aux`. + This may break Bash tools which rely on `ps` output. diff --git a/docs/javascript/images.md b/docs/javascript/images.md index d3c5c0bc4..e988c2af6 100644 --- a/docs/javascript/images.md +++ b/docs/javascript/images.md @@ -4,10 +4,6 @@ a. Option name for the file-loader and url-loader (todo reference) b. Option publicPath for the output (todo reference) - - - - ``` const assetLoaderRules = [ { @@ -35,10 +31,6 @@ const assetLoaderRules = [ ``` - - - - A full example can be found at [spec/dummy/client/app/components/ImageExample/ImageExample.jsx](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/client/app/components/ImageExample/ImageExample.jsx) You are free to use images either in image tags or as background images in SCSS files. You can diff --git a/docs/javascript/node-dependencies-and-npm.md b/docs/javascript/node-dependencies-and-npm.md index 3fb15f299..2abd824c1 100644 --- a/docs/javascript/node-dependencies-and-npm.md +++ b/docs/javascript/node-dependencies-and-npm.md @@ -4,11 +4,12 @@ You can check for outdated versions of packages with `yarn outdated` in your `client` directory. -To upgrade package version, use `yarn upgrade [package]`. To update all dependencies, use `yarn upgrade`. +To upgrade package version, use `yarn upgrade [package]`. To update all dependencies, use `yarn upgrade`. Confirm that the hot replacement dev server and the Rails server both work. ## Adding New Dependencies + Typically, you can add your Node dependencies as you normally would. ```bash diff --git a/docs/javascript/react-and-redux.md b/docs/javascript/react-and-redux.md index 1f7a9f59a..4521d4b18 100644 --- a/docs/javascript/react-and-redux.md +++ b/docs/javascript/react-and-redux.md @@ -1,12 +1,11 @@ # Communication between React Components and Redux Reducers ## Communication Between Components + See https://facebook.github.io/react/tips/communicate-between-components.html # Redux Reducers -Documentation of generated Redux code for reducers. -## Example The `helloWorld/reducers/index.jsx` example that results from running the generator with the Redux option may be slightly confusing because of its simplicity. For clarity, what follows is a more fleshed-out example of what a reducer might look like: ```javascript diff --git a/docs/javascript/react-helmet.md b/docs/javascript/react-helmet.md index 6452e6402..f8ec3f451 100644 --- a/docs/javascript/react-helmet.md +++ b/docs/javascript/react-helmet.md @@ -1,13 +1,15 @@ # Using React Helmet to build `` content ## Installation and general usage + See [nfl/react-helmet](https://github.com/nfl/react-helmet) for details on how to use this package. Run `yarn add react-helmet` to add this package to your application. ## Example + Here is what you need to do in order to configure your Rails application to work with **ReactHelmet**. - Create a render-function for server rendering like this: +Create a render-function for server rendering like this: ```javascript export default (props, _railsContext) => { @@ -21,19 +23,19 @@ export default (props, _railsContext) => { return { renderedHtml }; }; ``` + You can add more **helmet** properties to the result, e.g. **meta**, **base** and so on. See https://github.com/nfl/react-helmet#server-usage. Use a regular React functional or class component or a render-function for your client-side bundle: ```javascript // React functional component -export default (props) => ( - -); +export default (props) => ; ``` Or a render-function. Note you can't return just the JSX (React element), but you need to return either a React functional or class component. + ```javascript // React functional component export default (props, railsContext) => ( @@ -42,6 +44,7 @@ export default (props, railsContext) => ( ``` Note, this doesn't work, because this function just returns a React element rather than a React component + ```javascript // React functional component export default (props, railsContext) => ( @@ -50,6 +53,7 @@ export default (props, railsContext) => ( ``` Put the **ReactHelmet** component somewhere in your ``: + ```javascript import { Helmet } from 'react-helmet'; @@ -64,26 +68,30 @@ const App = (props) => ( export default App; ``` + Register your generators for client and server sides: ```javascript import ReactHelmetApp from '../ReactHelmetClientApp'; ReactOnRails.register({ - ReactHelmetApp + ReactHelmetApp, }); ``` + ```javascript // Note the import from the server file. import ReactHelmetApp from '../ReactHelmetServerApp'; ReactOnRails.register({ - ReactHelmetApp + ReactHelmetApp, }); ``` + Now when the `react_component_hash` helper is called with **"ReactHelmetApp"** as a first argument it will return a hash instead of HTML string. Note, there is no need to specify "prerender" as it would not make sense to use react_component_hash without server rendering: + ```ruby <% react_helmet_app = react_component_hash("ReactHelmetApp", props: { hello: "world" }, trace: true) %> @@ -95,6 +103,7 @@ make sense to use react_component_hash without server rendering: ``` So now we're able to insert received title tag to our application layout: + ```ruby <%= yield(:title) if content_for?(:title) %> ``` diff --git a/docs/javascript/react-router.md b/docs/javascript/react-router.md index e17b12665..64fc15b46 100644 --- a/docs/javascript/react-router.md +++ b/docs/javascript/react-router.md @@ -2,26 +2,22 @@ _This article needs updating for the latest version of React Router_ # Using React Router - React on Rails supports the use of React Router. Client-side code doesn't need any special configuration for the React on Rails gem. Implement React Router how you normally would. Note, you might want to avoid using Turbolinks as both Turbolinks and React-Router will be trying to handle the back and forward buttons. If you get this figured out, please do share with the community! Otherwise, you might have to tweak the basic settings for Turbolinks, and this may or may not be worth the effort. If you are working with the HelloWorldApp created by the react_on_rails generator, then the code below corresponds to the module in `client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx`. ```js - -import { BrowserRouter, Switch } from 'react-router-dom' -import routes from './routes.jsx' +import { BrowserRouter, Switch } from 'react-router-dom'; +import routes from './routes.jsx'; const RouterApp = (props, railsContext) => { // create your hydrated store const store = createStore(props); - + return ( - - {routes} - + {routes} ); @@ -30,61 +26,54 @@ const RouterApp = (props, railsContext) => { For a fleshed out integration of react_on_rails with react-router, check out [React Webpack Rails Tutorial Code](https://github.com/shakacode/react-webpack-rails-tutorial), specifically the files: -* [react-webpack-rails-tutorial/client/app/bundles/comments/routes/routes.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/routes/routes.jsx) - -* [react-webpack-rails-tutorial/client/app/bundles/comments/startup/ClientRouterApp.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/startup/ClientRouterApp.jsx) +- [react-webpack-rails-tutorial/client/app/bundles/comments/routes/routes.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/routes/routes.jsx) -* [react-webpack-rails-tutorial/client/app/bundles/comments/startup/ServerRouterApp.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/startup/ServerRouterApp.jsx) +- [react-webpack-rails-tutorial/client/app/bundles/comments/startup/ClientRouterApp.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/startup/ClientRouterApp.jsx) +- [react-webpack-rails-tutorial/client/app/bundles/comments/startup/ServerRouterApp.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/startup/ServerRouterApp.jsx) # Server Rendering Using React Router V4 -Your Render-Function may not return an object with the property `renderedHtml`. Thus, you call -renderToString() and return an object with this property. +Your Render-Function may not return an object with the property `renderedHtml`. Thus, you call +`renderToString()` and return an object with this property. This example **only applies to server rendering** and should be only used in the server side bundle. From the [original example in the ReactRouter docs](https://github.com/ReactTraining/react-router/blob/v4.3.1/packages/react-router-dom/docs/guides/server-rendering.md) - + ```javascript - import React from 'react' - import { renderToString } from 'react-dom/server' - import { StaticRouter } from 'react-router' - import { Provider } from 'react-redux' - import ReactOnRails from 'react-on-rails' - - // App.jsx from src/client/App.jsx - import App from '../App' - - const ReactServerRenderer = (props, railsContext) => { - const context = {} - - // commentStore from src/server/store/commentStore - const store = ReactOnRails.getStore('../store/commentStore') - - // Route Store generated from react-on-rails - - const { location } = railsContext - - const html = ReactDOMServer.renderToString( - - - - - - ) - - if (context.url) { - // Somewhere a `` was rendered - redirect(301, context.url) - } else { - // we're good, send the response - return { renderedHtml: html }; - } - } +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import { StaticRouter } from 'react-router'; +import { Provider } from 'react-redux'; +import ReactOnRails from 'react-on-rails'; + +// App.jsx from src/client/App.jsx +import App from '../App'; + +const ReactServerRenderer = (props, railsContext) => { + const context = {}; + + // commentStore from src/server/store/commentStore + const store = ReactOnRails.getStore('../store/commentStore'); + + // Route Store generated from react-on-rails + + const { location } = railsContext; + + const html = ReactDOMServer.renderToString( + + + + + , + ); + + if (context.url) { + // Somewhere a `` was rendered + redirect(301, context.url); + } else { + // we're good, send the response + return { renderedHtml: html }; } ``` diff --git a/docs/javascript/server-rendering-tips.md b/docs/javascript/server-rendering-tips.md index a36496d98..c83b010c0 100644 --- a/docs/javascript/server-rendering-tips.md +++ b/docs/javascript/server-rendering-tips.md @@ -2,13 +2,13 @@ For the best performance with Server Rendering, consider using [React on Rails Pro] - ## General Tips + - Your code can't reference `document`. Server side JS execution does not have access to `document`, so jQuery and some other libs won't work in this environment. You can debug this by putting in `console.log` statements in your code. - You can conditionally avoid running code that references document by either checking if `window` - is defined or using the "railsContext" + is defined or using the "railsContext" your top level react component. Since the passed in props Hash from the view helper applies to client and server side code, the best way to do this is to use a Render-Function. - If you're serious about server rendering, it's worth the effort to have different entry points for client and server rendering. It's worth the extra complexity. The point is that you have separate files for top level client or server side, and you pass some extra option indicating that rendering is happening server side. @@ -20,16 +20,18 @@ For the best performance with Server Rendering, consider using [React on Rails P 2. Be sure that `config.trace` is true. You will get the server invocation code that renders your component. If you're not using Shakapacker, you will also get the whole file used to setup the JavaScript context. ## CSS + Server bundles must always have CSS Extracted ## setTimeout, setInterval, and clearTimeout -These methods are polyfilled for server rendering to be no-ops. We log calls to these when in `trace` mode. In the past, some libraries, namely babel-polyfill, did call setTimout. +These methods are polyfilled for server rendering to be no-ops. We log calls to these when in `trace` mode. In the past, some libraries, namely babel-polyfill, did call setTimout. Here's an example of this which shows the line numbers that end up calling setTimeout: -``` + +```text ➜ ~/shakacode/react_on_rails/gen-examples/examples/basic-server-rendering (add-rails-helper-to-generator u=) ✗ export SERVER_TRACE_REACT_ON_RAILS=TRUE -➜ ~/shakacode/react_on_rails/gen-examples/examples/basic-server-rendering (add-rails-helper-to-generator u=) ✗ rspec +➜ ~/shakacode/react_on_rails/gen-examples/examples/basic-server-rendering (add-rails-helper-to-generator u=) ✗ rspec Hello World Building Webpack client-rendering assets... Completed building Webpack client-rendering assets. diff --git a/docs/javascript/troubleshooting-when-using-shakapacker.md b/docs/javascript/troubleshooting-when-using-shakapacker.md index 5925f76c2..cb3161c92 100644 --- a/docs/javascript/troubleshooting-when-using-shakapacker.md +++ b/docs/javascript/troubleshooting-when-using-shakapacker.md @@ -62,9 +62,9 @@ const { webpackConfig: baseClientWebpackConfig } = require('shakapacker'); config.optimization = baseClientWebpackConfig.optimization; ``` -As it set the `optimization.runtimeChunk` to `single`. See its source: -`package/environments/base.js:115` +As it set the `optimization.runtimeChunk` to `single`. [See its source](https://github.com/shakacode/shakapacker/blob/cdf32835d3e0949952b8b4b53063807f714f9b24/package/environments/base.js#L115-L119): + ```js optimization: { splitChunks: { chunks: 'all' }, @@ -72,6 +72,5 @@ As it set the `optimization.runtimeChunk` to `single`. See its source: runtimeChunk: 'single' }, ``` -https://github.com/shakacode/shakapacker/blob/cdf32835d3e0949952b8b4b53063807f714f9b24/package/environments/base.js#L115-L119 Or set `optimization.runtimeChunk` to `single` directly. diff --git a/docs/javascript/troubleshooting-when-using-webpacker.md b/docs/javascript/troubleshooting-when-using-webpacker.md index 051fd3d82..c03290666 100644 --- a/docs/javascript/troubleshooting-when-using-webpacker.md +++ b/docs/javascript/troubleshooting-when-using-webpacker.md @@ -6,9 +6,10 @@ react_on_rails: upgraded from 6.6.0 to 9.0.3 ## The failure Rspec failing with -``` + +```text Failure/Error: raise Webpacker::Manifest::MissingEntryError, missing_file_from_manifest_error(name) - + Webpacker::Manifest::MissingEntryError: Webpacker can't find webpack-bundle.js in /home/user/ws/pp/code/pp-core-checkout_spa_update_npm/public/webpack-test/manifest.json. Possible causes: 1. You want to set webpacker.yml value of compile to true for your environment diff --git a/docs/javascript/webpack-v1-notes.md b/docs/javascript/webpack-v1-notes.md index e7bd2e09d..5962053f5 100644 --- a/docs/javascript/webpack-v1-notes.md +++ b/docs/javascript/webpack-v1-notes.md @@ -3,10 +3,11 @@ The following only apply to Webpack V1. Take 1 hour and update to v2! It's worth it! ## Use the `--bail` Option When Running Webpack for CI or Deployments if using Webpack V1 -For your scripts that statically build your Webpack bundles, use the `--bail` option. This will ensure that CI and your product deployment **halt** if Webpack cannot complete! For more details, see the documentation for [Webpack's `--bail` option](https://webpack.js.org/configuration/other-options/#bail). Note, you might not want to use the `--bail` option if you just want to depend on Webpack returning a non-zero error code and you want to see all the errors, rather than only the first error. +For your scripts that statically build your Webpack bundles, use the `--bail` option. This will ensure that CI and your product deployment **halt** if Webpack cannot complete! For more details, see the documentation for [Webpack's `--bail` option](https://webpack.js.org/configuration/other-options/#bail). Note, you might not want to use the `--bail` option if you just want to depend on Webpack returning a non-zero error code and you want to see all the errors, rather than only the first error. ## Entry Points + You should ensure you configure the entry points correctly for webpack if you want to break out libraries into a "vendor" bundle where your libraries are packaged separately from your app's code. If you send web clients your vendor bundle separately from your app bundles, then web clients might have the vendor bundle cached while they receive updates for your app. You need both include `react-dom` and `react` as values for `entry`, like this: diff --git a/docs/javascript/webpack.md b/docs/javascript/webpack.md index a2a083d9c..b48a1b05c 100644 --- a/docs/javascript/webpack.md +++ b/docs/javascript/webpack.md @@ -1,13 +1,12 @@ # Webpack Tips -## Where do I learn about advanced Webpack setups, such as with "CSS Modules", "Code Splitting", etc -You can try out example app, [shakacode/react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial). We're building comprehensive production examples in our new, premium product, [**React on Rails Pro**](https://forum.shakacode.com/t/introducing-react-on-rails-pro-subscriptions/785). If you're interested, please see the details in [this forum post](https://forum.shakacode.com/t/introducing-react-on-rails-pro-subscriptions/785). +## Where can I learn about advanced Webpack setups, including e.g. "CSS Modules", "Code Splitting", etc.? -## Webpack v1 or v2? -We recommend using Webpack version 2.3.1 or greater. +You can try our example app, [shakacode/react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial). We're building comprehensive production examples in our new, premium product, [**React on Rails Pro**](https://forum.shakacode.com/t/introducing-react-on-rails-pro-subscriptions/785). If you're interested, please see the details in [this forum post](https://forum.shakacode.com/t/introducing-react-on-rails-pro-subscriptions/785). ## yarn or npm? -Yarn is the current recommendation! + +Yarn v1 is our current recommendation! ## Entry Points @@ -15,8 +14,11 @@ You should ensure you configure the entry points correctly for webpack if you wa Webpack v2 makes this very convenient! See: -* [Implicit Common Vendor Chunk](https://webpack.js.org/guides/code-splitting-libraries/#implicit-common-vendor-chunk) -* [Manifest File](https://webpack.js.org/guides/code-splitting-libraries/#manifest-file) +- [Implicit Common Vendor Chunk](https://webpack.js.org/guides/code-splitting-libraries/#implicit-common-vendor-chunk) +- [Manifest File](https://webpack.js.org/guides/code-splitting-libraries/#manifest-file) + +## Webpack v5 + +Webpack v5 is highly recommended. See [the release post](https://webpack.js.org/blog/2020-10-10-webpack-5-release/) and [the official migration documentation](https://webpack.js.org/migrate/5/). -## Webpack v4 -Webpack v4 is heartily recommended. If you need help with migrating your project to Webpack v4, please contact me, [justin@shakacode.com](mailto:justin@shakacode.com). +If you need help with migrating your project to Webpack v5, please contact Justin Gordon at [justin@shakacode.com](mailto:justin@shakacode.com). diff --git a/docs/misc/articles.md b/docs/misc/articles.md index 3555de180..093f6852e 100644 --- a/docs/misc/articles.md +++ b/docs/misc/articles.md @@ -2,19 +2,20 @@ ## Articles -* [Introducing React on Rails v9 with Webpacker Support](https://blog.shakacode.com/introducing-react-on-rails-v9-with-webpacker-support-f2584c6c8fa4) for an overview of the integration of React on Rails with Webpacker. -* [Webpacker Lite: Why Fork Webpacker?](https://blog.shakacode.com/webpacker-lite-why-fork-webpacker-f0a7707fac92) -* [React on Rails, 2000+ 🌟 Stars](https://medium.com/shakacode/react-on-rails-2000-stars-32ff5cfacfbf#.6gmfb2gpy) -* [The React on Rails Doctrine](https://medium.com/@railsonmaui/the-react-on-rails-doctrine-3c59a778c724) -* [Simple Tutorial](https://www.shakacode.com/react-on-rails/docs/guides/tutorial/). +- [Introducing React on Rails v9 with Webpacker Support](https://blog.shakacode.com/introducing-react-on-rails-v9-with-webpacker-support-f2584c6c8fa4) for an overview of the integration of React on Rails with Webpacker. +- [Webpacker Lite: Why Fork Webpacker?](https://blog.shakacode.com/webpacker-lite-why-fork-webpacker-f0a7707fac92) +- [React on Rails, 2000+ 🌟 Stars](https://medium.com/shakacode/react-on-rails-2000-stars-32ff5cfacfbf#.6gmfb2gpy) +- [The React on Rails Doctrine](https://medium.com/@railsonmaui/the-react-on-rails-doctrine-3c59a778c724) +- [Simple Tutorial](https://www.shakacode.com/react-on-rails/docs/guides/tutorial/). ## Videos -* [Video of running the v9 installer with Webpacker v3](https://youtu.be/M0WUM_XPaII). History, motivations, philosophy, and overview. +- [Video of running the v9 installer with Webpacker v3](https://youtu.be/M0WUM_XPaII). History, motivations, philosophy, and overview. + 1. [GORUCO 2017: Front-End Sadness to Happiness: The React on Rails Story by Justin Gordon](https://www.youtube.com/watch?v=SGkTvKRPYrk) 2. [egghead.io: Creating a component with React on Rails](https://egghead.io/lessons/react-creating-a-component-with-react-on-rails) 3. [egghead.io: Creating a redux component with React on Rails](https://egghead.io/lessons/react-add-redux-state-management-to-a-react-on-rails-project) 4. [React On Rails Tutorial Series](https://www.youtube.com/playlist?list=PL5VAKH-U1M6dj84BApfUtvBjvF-0-JfEU) - 1. [History and Motivation](https://youtu.be/F4oymbUHvoY) - 2. [Basic Tutorial Walkthrough](https://youtu.be/_bjScw60FBk) - 3. [Code Walkthrough](https://youtu.be/McQ9UM-_ocQ) + 1. [History and Motivation](https://youtu.be/F4oymbUHvoY) + 2. [Basic Tutorial Walkthrough](https://youtu.be/_bjScw60FBk) + 3. [Code Walkthrough](https://youtu.be/McQ9UM-_ocQ) diff --git a/docs/misc/doctrine.md b/docs/misc/doctrine.md index dade58aff..092593185 100644 --- a/docs/misc/doctrine.md +++ b/docs/misc/doctrine.md @@ -9,6 +9,7 @@ As stated in the [React on Rails README](https://www.shakacode.com/react-on-rail Besides the project objective, let's stick with the "Rails Doctrine" and keep the following in mind. ## Optimize for Programmer Happiness + The React on Rails setup provides several key components related to front-end developer happiness: 1. [Hot reloading of both JavaScript and CSS](https://gaearon.github.io/react-hot-loader/), via the [webpack dev server](https://webpack.github.io/docs/webpack-dev-server.html). This works for both using an [express server](http://expressjs.com/) to load stubs for the ajax requests, as well as using a live Rails server. **Oh yes**, your Rails server can do hot reloading! @@ -22,56 +23,65 @@ The React on Rails setup provides several key components related to front-end de 9. Just because we're not relying on the Rails asset pipeline for ES6 conversion does not mean that we're deploying Rails apps in any different way. We still use the asset pipeline to include our Webpack compiled JavaScript. This only requires a few small modifications, as explained in [our heroku deployment documentation](https://www.shakacode.com/react-on-rails/docs/deployment/heroku-deployment/). ## Convention over Configuration -* React on Rails has taken the hard work out of figuring out the JavaScript tooling that works best with Rails. Not only could you spend lots of time researching different tooling, but then you'd have to figure out how to splice it all together. This is where a lot of "JavaScript fatigue" comes from. The following keep the code clean and consistent: -* [Style Guide](https://www.shakacode.com/react-on-rails/docs/misc/style/) -* [linters](https://www.shakacode.com/react-on-rails/docs/contributor-info/linters/) + +- React on Rails has taken the hard work out of figuring out the JavaScript tooling that works best with Rails. Not only could you spend lots of time researching different tooling, but then you'd have to figure out how to splice it all together. This is where a lot of "JavaScript fatigue" comes from. The following keep the code clean and consistent: +- [Style Guide](https://www.shakacode.com/react-on-rails/docs/misc/style/) +- [linters](https://www.shakacode.com/react-on-rails/docs/contributor-info/linters/) We're big believers in this quote from the Rails Doctrine: > The same goes even when you understand how all the pieces go together. When there’s an obvious next step for every change, we can scoot through the many parts of an application that is the same or very similar to all the other applications that went before it. A place for everything and everything in its place. Constraints liberate even the most able minds. - ## The Menu Is Omakase + Here's the chef's selection from the React on Rails community: ### Libraries -* [Bootstrap](http://getbootstrap.com/), loaded from [bootstrap-loader](https://github.com/shakacode/bootstrap-loader/). Common UI styles. -* [Lodash](https://lodash.com/): Swiss army knife of utilities. -* [React](https://facebook.github.io/react/): UI components. -* [React-Router](https://github.com/reactjs/react-router): Provider of deep links for client-side application. -* [Redux](https://github.com/reactjs/redux): Flux implementation (aka "state container"). + +- [Bootstrap](http://getbootstrap.com/), loaded from [bootstrap-loader](https://github.com/shakacode/bootstrap-loader/). Common UI styles. +- [Lodash](https://lodash.com/): Swiss army knife of utilities. +- [React](https://facebook.github.io/react/): UI components. +- [React-Router](https://github.com/reactjs/react-router): Provider of deep links for client-side application. +- [Redux](https://github.com/reactjs/redux): Flux implementation (aka "state container"). ### JavaScript Tooling -* [Babel](https://babeljs.io/): Transpiler for ES6 into ES5 and much more. -* [EsLint](http://eslint.org/) -* [Webpack](http://webpack.github.io/): Generator of deployment assets and provider of hot module reloading. + +- [Babel](https://babeljs.io/): Transpiler for ES6 into ES5 and much more. +- [ESLint](http://eslint.org/) +- [Webpack](http://webpack.github.io/): Generator of deployment assets and provider of hot module reloading. By having a common set of tools, we can discuss what we should or shouldn't be using. Thus, this takes out the "JavaScript Fatigue" from having too many unclear choices in the JavaScript world. -By the way, we're *not* omakase for standard Rails. That would be CoffeeScript. However, the Rails Doctrine makes it clear that non-standard menu choices are certainly welcome! +By the way, we're _not_ omakase for standard Rails. That would be CoffeeScript. However, the Rails Doctrine makes it clear that non-standard menu choices are certainly welcome! ## No One Paradigm -React on Rails fits into the "No One Paradigm" of the Rails ecosystem from the perspective that it rocks for client side development with Rails, even though it's a totally different language than the server code written in Ruby. + +React on Rails fits into the "No One Paradigm" of the Rails ecosystem from the perspective that it rocks for client-side development with Rails, even though it's a totally different language than the server code written in Ruby. ## Exalt Beautiful Code + ES5 was ugly. ES6 is beautiful. React is beautiful. Client side code written with React plus Redux, fully linted with the ShakaCode linters, and organized per our recommended project structure is beautiful. Don't take our word for it. Take a look at the component sample code in the [react-webpack-rails-tutorial sample code](https://github.com/shakacode/react-webpack-rails-tutorial/tree/master/client/app/bundles/comments). ## Value Integrated Systems -Assuming that you're building the type of app that's a good fit for Rails (document/database based with lots of business rules), the tight integration of modern JavaScript with React on top of Ruby on Rails is better than building a pure client side app and separate microservices. Here's why: -* Via React on Rails, we can seamlessly integrate React UI components with Rails. -* Tight integration allows for trivial set up of server rendering of React on top of Rails, complete with support for fragment caching of the server rendered HTML, and integration with [Turbolinks](https://github.com/turbolinks/turbolinks). -* Tight integration allows mixing and matching Rails pages with React driven pages, even on the same page. Not every part of a UI requires the high fidelity achievable using React. Many existing apps may have hundreds of standards Rails forms. Support for mixing and matching React with Rails forms provides the best of both worlds. +Assuming that you're building the type of app that's a good fit for Rails (document/database based with lots of business rules), the tight integration of modern JavaScript with React on top of Ruby on Rails is better than building a pure client-side app and separate microservices. Here's why: + +- Via React on Rails, we can seamlessly integrate React UI components with Rails. +- Tight integration allows for trivial set up of server rendering of React on top of Rails, complete with support for fragment caching of the server rendered HTML, and integration with [Turbolinks](https://github.com/turbolinks/turbolinks). +- Tight integration allows mixing and matching Rails pages with React-driven pages, even on the same page. Not every part of a UI requires the high fidelity achievable using React. Many existing apps may have hundreds of standard Rails forms. Support for mixing and matching React with Rails forms provides the best of both worlds. ## Progress over Stability + React on Rails will maintain an active pace of development, to keep up with: -* Community suggestions. -* New client side tooling, libraries, and techniques. -* Updates to Rails. +- Community suggestions. +- New client side tooling, libraries, and techniques. +- Updates to Rails. ## Raise a Big Tent + React on Rails is definitely a part of the big tent of Rails. Plus, React on Rails provides its own big tent. A huge benefit of the React on Rails system is simple integration with Webpack and NPM, allowing integration with almost any library available on [npm](https://www.npmjs.org/)! The integration with Webpack also allows for other Webpack supported build tools. ## Thanks! -Thanks for reading and being a part of the React on Rails community. Feedback on this document and *anything* in React on Rails is welcome. Please [open an issue](https://github.com/shakacode/react_on_rails/issues/new) or a pull request. If you'd like to join our private Slack channel, please [email](mailto:contact@shakacode.com) us a request. + +Thanks for reading and being a part of the React on Rails community. Feedback on this document and _anything_ in React on Rails is welcome. Please [open an issue](https://github.com/shakacode/react_on_rails/issues/new) or a pull request. If you'd like to join our private Slack channel, please [email](mailto:contact@shakacode.com) us a request. diff --git a/docs/misc/style.md b/docs/misc/style.md index 9300e6b0a..4d765034e 100644 --- a/docs/misc/style.md +++ b/docs/misc/style.md @@ -1,33 +1,41 @@ # Code Style + This document describes the coding style of [ShakaCode](http://www.shakacode.com). Yes, it's opinionated, as all style guidelines should be. We shall put as little as possible into this guide and instead rely on: -* Use of linters with our standard linter configuration. -* References to existing style guidelines that support the linter configuration. -* Anything additional goes next. +- Use of linters with our standard linter configuration. +- References to existing style guidelines that support the linter configuration. +- Anything additional goes next. ## Client Side JavaScript and React -* See the [Shakacode JavaScript Style Guide](https://github.com/shakacode/style-guide-javascript) + +- See the [Shakacode JavaScript Style Guide](https://github.com/shakacode/style-guide-javascript) ## Style Guides to Follow + Follow these style guidelines per the linter configuration. Basically, lint your code and if you have questions about the suggested fixes, look here: ### Ruby Coding Standards -* [ShakaCode Ruby Coding Standards](https://github.com/shakacode/style-guide-ruby) -* [Ruby Documentation](http://guides.rubyonrails.org/api_documentation_guidelines.html) + +- [ShakaCode Ruby Coding Standards](https://github.com/shakacode/style-guide-ruby) +- [Ruby Documentation](http://guides.rubyonrails.org/api_documentation_guidelines.html) ### JavaScript Coding Standards -* [ShakaCode Javascript](https://github.com/shakacode/style-guide-javascript) -* Use the [eslint-config-shakacode](https://github.com/shakacode/style-guide-javascript/tree/master/packages/eslint-config-shakacode) npm package with eslint. -* [JSDoc](http://usejsdoc.org/) + +- [ShakaCode Javascript](https://github.com/shakacode/style-guide-javascript) +- Use the [eslint-config-shakacode](https://github.com/shakacode/style-guide-javascript/tree/master/packages/eslint-config-shakacode) NPM package with ESLint. +- [JSDoc](http://usejsdoc.org/) ### Git coding Standards -* [Git Coding Standards](http://chlg.co/1GV2m9p) + +- [Git Coding Standards](http://chlg.co/1GV2m9p) ### Sass Coding Standards -* [Sass Guidelines](http://sass-guidelin.es/) by [Hugo Giraudel](http://hugogiraudel.com/) -* [Github Front End Guidelines](http://primercss.io/guidelines/) + +- [Sass Guidelines](http://sass-guidelin.es/) by [Hugo Giraudel](http://hugogiraudel.com/) +- [Github Front End Guidelines](http://primercss.io/guidelines/) # Git Usage -* Follow a github-flow model where you branch off of master for features. -* Before merging a branch to master, rebase it on top of master, by using command like `git fetch; git checkout my-branch; git rebase -i origin/master`. Clean up your commit message at this point. Be super careful to communicate with anybody else working on this branch and do not do this when others have uncommitted changes. Ideally, your merge of your feature back to master should be one nice commit. -* Run hosted CI and code coverage. + +- Follow a [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) model where you branch off master for features. +- Before merging a branch to master, rebase it on top of master, by using command like `git fetch; git checkout my-branch; git rebase -i origin/master`. Clean up your commit message at this point. Be super careful to communicate with anybody else working on this branch and do not do this when others have uncommitted changes. Ideally, your merge of your feature back to master should be one nice commit. +- Run hosted CI and code coverage. diff --git a/docs/misc/tips.md b/docs/misc/tips.md index 52529e11b..f2f7ff3ef 100644 --- a/docs/misc/tips.md +++ b/docs/misc/tips.md @@ -1,10 +1,12 @@ # Tips -+ **DO NOT RUN `rails s`** and instead run - `foreman start -f Procfile.dev` +- **DO NOT RUN `rails s`** and instead run - to automatically start the webpack file watchers that will regenerate your JavaScript. Note, RSpec does not automatically rebuild the bundle files, so you could get incorrect results from your tests if you change the client code and do not rebuild the bundles. The same problem occurs when pulling down changes from GitHub and running tests without first rebuilding the bundles. -+ The default for rendering right now is `prerender: false`. **NOTE:** Server side rendering does not work for some components that use an async setup for server rendering. You can configure the default for prerender in your config. -+ You can expose either a React component or a function that returns a React component. If you wish to create a React component via a function, rather than simply props, then you need to set the property "generator" on that helper invocation to true (or change the defaults). When that is done, the function is invoked with a single parameter of "props", and that function should return a React element. -+ Be sure you can first render your react component **client only** before you try to debug server rendering! -+ Open up the HTML source and take a look at the generated HTML and the JavaScript to see what's going on under the covers. Note that when server rendering is turned on, then you'll see the server rendered react components. When server rendering is turned off, then you'll only see the `div` element where the in-line JavaScript will render the component. You might also notice how the props you pass (a Ruby Hash) becomes in-line JavaScript on the HTML page. + `foreman start -f Procfile.dev` + + to automatically start the webpack file watchers that will regenerate your JavaScript. Note, RSpec does not automatically rebuild the bundle files, so you could get incorrect results from your tests if you change the client code and do not rebuild the bundles. The same problem occurs when pulling down changes from GitHub and running tests without first rebuilding the bundles. + +- The default for rendering right now is `prerender: false`. **NOTE:** Server side rendering does not work for some components that use an async setup for server rendering. You can configure the default for prerender in your config. +- You can expose either a React component or a function that returns a React component. If you wish to create a React component via a function, rather than simply props, then you need to set the property "generator" on that helper invocation to true (or change the defaults). When that is done, the function is invoked with a single parameter of "props", and that function should return a React element. +- Be sure you can first render your react component **client only** before you try to debug server rendering! +- Open up the HTML source and take a look at the generated HTML and the JavaScript to see what's going on under the covers. Note that when server rendering is turned on, then you'll see the server rendered react components. When server rendering is turned off, then you'll only see the `div` element where the in-line JavaScript will render the component. You might also notice how the props you pass (a Ruby Hash) becomes in-line JavaScript on the HTML page. diff --git a/docs/outdated/deferred-rendering.md b/docs/outdated/deferred-rendering.md index 0509a1c27..4b858e0a8 100644 --- a/docs/outdated/deferred-rendering.md +++ b/docs/outdated/deferred-rendering.md @@ -3,7 +3,7 @@ Please see [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/) if you are interested in code splitting using [loadable-components.com](https://loadable-components.com/docs) with React on Rails. ------ +--- What is code splitting? From the webpack documentation: @@ -25,14 +25,13 @@ Different markup is generated on the client than on the server. Why does this ha To prevent this, you have to wait until the code chunk is fetched before doing the initial render on the client side. To accomplish this, react on rails allows you to register a renderer. This works just like registering a Render-Function, except that the function you pass takes three arguments: `renderer(props, railsContext, domNodeId)`, and is responsible for calling `ReactDOM.render` or `ReactDOM.hydrate` to render the component to the DOM. React on rails will automatically detect when a Render-Function takes three arguments, and will **not** call `ReactDOM.render` or `ReactDOM.hydrate`, instead allowing you to control the initial render yourself. Note, you have to be careful to call `ReactDOM.hydrate` rather than `ReactDOM.render` if you are server rendering. - ## Server vs. Client Code Caveats If you're going to try to do code splitting with server rendered routes, you'll probably need to use separate route definitions for client and server to prevent code splitting from happening for the server bundle. The server bundle should be one file containing all the JavaScript code. This will require you to have separate webpack configurations for client and server. Do not attempt to register a renderer function on the server. Instead, register either a Render-Function or a component. If you register a renderer in the server bundle, you'll get an error when react on rails tries to server render the component. - ## React on Rails Pro + [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/) includes a complete setup using this technique for code splitting using [loadable-components.com](https://loadable-components.com/docs) with React on Rails. diff --git a/docs/outdated/rails-assets-relative-paths.md b/docs/outdated/rails-assets-relative-paths.md index d6e2deae8..0622918f2 100644 --- a/docs/outdated/rails-assets-relative-paths.md +++ b/docs/outdated/rails-assets-relative-paths.md @@ -1,4 +1,4 @@ -*Note: this doc reflects using Sprockets for assets and has not been updated for Shakapacker or rails/webpacker* +_Note: this doc reflects using Sprockets for assets and has not been updated for Shakapacker or rails/webpacker_ # Using Webpack bundled assets with the Rails Asset Pipeline @@ -36,17 +36,16 @@ Once you have added file-loader (or whatever loader you would like to use) to yo 2. `loader`: the name of the loader you will be using (in this doc we will be using [file-loader](https://github.com/webpack-contrib/file-loader)) 3. `query`: query parameters are additional configuration options that get passed to the loader. This can either be appended to your `loader` attribute like follows: - ```javascript -loader: "file-loader?name=[name].[ext]" +loader: 'file-loader?name=[name].[ext]', ``` or as a JSON object: ```javascript query: { - name: "[name].[ext]" -} + name: '[name].[ext]', +}, ``` both of these two example above do the exact same thing, just using different syntaxes. For the rest of this doc we will be using the JSON object style. For more information about webpack loaders, read [this](https://webpack.github.io/docs/using-loaders.html). @@ -54,7 +53,7 @@ both of these two example above do the exact same thing, just using different sy _For the sake of this doc, we're also going to add a `resolve["alias"]` inside our webpack.config to make it easier to include our assets in our jsx files. In `resolve["alias"]`, simply add:_ ```javascript -'assets': path.resolve('./app/assets') +'assets': path.resolve('./app/assets'), ``` ##### Configuring your file-loader Query Parameters @@ -62,7 +61,7 @@ _For the sake of this doc, we're also going to add a `resolve["alias"]` inside o The first property we'll want to set is our file's resulting name after bundling. For now we're just going to use: ```javascript -name: "[name][md5:hash].[ext]" +name: '[name][md5:hash].[ext]', ``` This will just set the name to the file's original name + a md5 digested hash + the extension of the original file (.png, .jpg, etc). @@ -70,7 +69,7 @@ This will just set the name to the file's original name + a md5 digested hash + Next we'll set the outputPath for our files. This is the directory we want the files to be placed in after webpack runs. When Webpack runs with file-loader, all files (in this case assets) that have been used in the bundled JavaScript will be bundled and outputted to the output destination. **Keep in mind that react_on_rails outputs by default to the `app/assets/webpack/` directory so when we specify the outputPath here it will be relative the `app/assets/webpack` directory.** You can set the outputPath to whatever you want, in this example we will add it to a directory `/app/assets/webpack/webpack-assets/`, and here's how we would do that: ```javascript -outputPath: "webpack-assets/" +outputPath: 'webpack-assets/', ``` Note: _You can output these files in the asset pipeline wherever you see fit. My preference is outputting somewhere inside the `app/assets/webpack/` directory just because anything in this directory is already ignored by git due to the react_on_rails generated gitignore, meaning they will not be added by git twice! (once in your `client/app/assets/` and once in your outputted path after webpack bundling)_ @@ -82,7 +81,7 @@ Note: _If you're having a hard time figuring out what an asset's path will be on Our publicPath setting will match the path to our outputted assets on our rails web server. Given our assets in this example will be outputted to `/app/assets/webpack/webpack-assets/` and hosted at `/assets/webpack-assets/`, our publicPath would be: ```javascript -publicPath: "/assets/webpack-assets/" +publicPath: '/assets/webpack-assets/', ``` Voila! Your webpack setup is complete. @@ -92,7 +91,8 @@ Voila! Your webpack setup is complete. Now for the fun part, we actually get to use our client assets now. The first thing you'll want to do is create an assets directory inside your client directory. The best place for this directory is probably at `client/app/assets`. Put any assets you want in there, images, stylesheets, whatever. Now that the assets are in place, we can simply `import` or `require` them in our jsx files for use in our components. For example: ```javascript -import myImage from 'assets/images/my-image.png'; // This uses the assets alias we created earlier to map to the client/app/assets/ directory followed by `images/my-image.png` +// This uses the assets alias we created earlier to map to the client/app/assets/ directory followed by `images/my-image.png` +import myImage from 'assets/images/my-image.png'; export default class MyImageBox extends React.Component { constructor(props, context) { @@ -100,7 +100,7 @@ export default class MyImageBox extends React.Component { } render() { - return + return ; } } ``` @@ -133,13 +133,11 @@ const devBuild = process.env.NODE_ENV !== 'production'; const nodeEnv = devBuild ? 'development' : 'production'; module.exports = { - entry: [ - './app/bundles/HelloWorld/startup/registration', - ], + entry: ['./app/bundles/HelloWorld/startup/registration'], output: { filename: 'hello-world-bundle.js', - path: '../app/assets/webpack' + path: '../app/assets/webpack', }, resolve: { @@ -155,8 +153,8 @@ module.exports = { new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(nodeEnv), - } - }) + }, + }), ], module: { rules: [ @@ -173,7 +171,7 @@ module.exports = { shim: 'es5-shim/es5-shim', sham: 'es5-shim/es5-sham', }, - } + }, }, { // The important stuff @@ -183,12 +181,12 @@ module.exports = { options: { name: '[name][md5:hash].[ext]', // Name of bundled asset outputPath: 'webpack-assets/', // Output location for assets. Final: `app/assets/webpack/webpack-assets/` - publicPath: '/assets/webpack-assets/' // Endpoint asset can be found at on Rails server - } - } - } - ] - } + publicPath: '/assets/webpack-assets/', // Endpoint asset can be found at on Rails server + }, + }, + }, + ], + }, }; ``` diff --git a/docs/outdated/rails-assets.md b/docs/outdated/rails-assets.md index 2eb5606af..8c211970a 100644 --- a/docs/outdated/rails-assets.md +++ b/docs/outdated/rails-assets.md @@ -1,18 +1,19 @@ # Rails assets and the Extract Text Plugin -*This doc needs updating for the use of Shakapacker or rails/webpacker with React on Rails* +_This doc needs updating for the use of Shakapacker or rails/webpacker with React on Rails_ The [Webpack file loader](https://github.com/webpack/file-loader) copies referenced files to the destination output directory, with an MD5 hash. The other term for this is a "digest". > By default the filename of the resulting file is the MD5 hash of the file's contents with -the original extension of the required resource. +> the original extension of the required resource. The most common use cases for Webpack processed files are images used for backgrounds in CSS and fonts for CSS. However, this applies to any file that might be processed using the Webpack file loader. ## The Problem + To understand the problem, it helps to read this article: [What is fingerprinting and why should I care](http://guides.rubyonrails.org/asset_pipeline.html#what-is-fingerprinting-and-why-should-i-care-questionmark) Basically, when Rails prepares assets for production deployments, it also adds a digest @@ -33,6 +34,7 @@ fail to load. _If you are interested in learning how to use assets in your React components, read this doc: [Webpack, the Asset Pipeline, and Using Assets w/ React](https://www.shakacode.com/react-on-rails/docs/outdated/rails-assets-relative-paths/)_ ## The Solution: Symlink Original File Names to New File Names + _Note, this solution was removed in v14. If you're interested in this symlink solution, please create a github issue._ diff --git a/docs/outdated/rails3.md b/docs/outdated/rails3.md index f2c827aad..7d330bd5e 100644 --- a/docs/outdated/rails3.md +++ b/docs/outdated/rails3.md @@ -1,8 +1,8 @@ # Rails 3 -* Please let us know if you find any issues with Rails 3. -* Rails 3 is confirmed to work up with versions up to 6.8.x. -* We are not testing it for new releases. If you find an issue, you will have to submit a PR to get it fixed. +- Please let us know if you find any issues with Rails 3. +- Rails 3 is confirmed to work up with versions up to 6.8.x. +- We are not testing it for new releases. If you find an issue, you will have to submit a PR to get it fixed. ## Known Issues diff --git a/docs/rails/rails-engine-integration.md b/docs/rails/rails-engine-integration.md index 5d6efa065..c598708e0 100644 --- a/docs/rails/rails-engine-integration.md +++ b/docs/rails/rails-engine-integration.md @@ -1,19 +1,25 @@ ## In your engine -+ At the top of `config/initializers/react_on_rails.rb` +- At the top of `config/initializers/react_on_rails.rb` + ```ruby ActiveSupport.on_load(:action_view) do include ReactOnRailsHelper end ``` -+ In your `.gemspec`: + +- In your `.gemspec`: + ```ruby s.add_dependency 'react_on_rails', '~> 6' ``` -+ In your `lib/.rb` (the entry point for your engine) + +- In your `lib/.rb` (the entry point for your engine) + ```ruby require "react_on_rails" ``` + ## In the project including your engine Place `gem 'react_on_rails', '~> 6'` before the gem pointing at your engine in your gemfile. @@ -26,7 +32,7 @@ Another solution would be to detach this rake task from the `rails assets:precom # Github Issues -* [Integration with an engine #342](https://github.com/shakacode/react_on_rails/issues/342) -* [Feature: target destination option for the install generator #459](https://github.com/shakacode/react_on_rails/issues/459) -* [Integration with Rails 5 Engines #562](https://github.com/shakacode/react_on_rails/issues/562) -* [Run inside a Rails engine? #257](https://github.com/shakacode/react_on_rails/issues/257) +- [Integration with an engine #342](https://github.com/shakacode/react_on_rails/issues/342) +- [Feature: target destination option for the install generator #459](https://github.com/shakacode/react_on_rails/issues/459) +- [Integration with Rails 5 Engines #562](https://github.com/shakacode/react_on_rails/issues/562) +- [Run inside a Rails engine? #257](https://github.com/shakacode/react_on_rails/issues/257) diff --git a/docs/rails/rails_view_rendering_from_inline_javascript.md b/docs/rails/rails_view_rendering_from_inline_javascript.md index b437c4c37..356a7616b 100644 --- a/docs/rails/rails_view_rendering_from_inline_javascript.md +++ b/docs/rails/rails_view_rendering_from_inline_javascript.md @@ -1,4 +1,5 @@ # Using ReactOnRails in JavaScript + You can easily render React components in your JavaScript with `render` method that returns a [reference to the component](https://facebook.github.io/react/docs/more-about-refs.html) (virtual DOM element). ```js @@ -15,10 +16,11 @@ You can easily render React components in your JavaScript with `render` method t * @param hydrate [optional] Pass truthy to update server rendered html. Default is falsy * @returns {virtualDomElement} Reference to your component's backing instance */ -ReactOnRails.render(componentName, props, domNodeId) +ReactOnRails.render(componentName, props, domNodeId); ``` ## Why do we need this? + Imagine that we have some event with jQuery, it allows us to set component state manually. ```html @@ -26,10 +28,10 @@ Imagine that we have some event with jQuery, it allows us to set component state
diff --git a/docs/rails/turbolinks.md b/docs/rails/turbolinks.md index 98e3f1f89..2cddf1b29 100644 --- a/docs/rails/turbolinks.md +++ b/docs/rails/turbolinks.md @@ -1,58 +1,69 @@ # Turbolinks and Turbo Support ## React on Rails Updated to support Turbo, August 2024 -* See [PR 1620](https://github.com/shakacode/react_on_rails/pull/1620). -* See [PR 1374](https://github.com/shakacode/react_on_rails/pull/1374). -* Ability to use with Turbo (@hotwired/turbo), as Turbolinks gets obsolete. + +- See [PR 1620](https://github.com/shakacode/react_on_rails/pull/1620). +- See [PR 1374](https://github.com/shakacode/react_on_rails/pull/1374). +- Ability to use with Turbo (@hotwired/turbo), as Turbolinks gets obsolete. # Using Turbo To configure Turbo the following option can be set: - `ReactOnRails.setOptions({ turbo: true })` +`ReactOnRails.setOptions({ turbo: true })` Turbo is not auto-detected like older Turbolinks. _TODO: Walk through code changes in PR 1620._ # Legacy Turbolinks -*The below docs may be outdated. We recommend updating to the latest Turbo or removing old Turbolinks.* -* See [Turbolinks on Github](https://github.com/rails/turbolinks) -* React on Rails currently supports 2.5.x of Turbolinks and 5.0.0 of Turbolinks 5. -* You may include Turbolinks either via yarn (recommended) or via the gem. +_The following docs may be outdated. We recommend updating to the latest Turbo or removing old Turbolinks._ + +- See [Turbolinks on Github](https://github.com/rails/turbolinks) +- React on Rails currently supports 2.5.x of Turbolinks and 5.0.0 of Turbolinks 5. +- You may include Turbolinks either via yarn (recommended) or via the gem. ## Why Turbolinks? + As you switch between Rails HTML controller requests, you will only load the HTML and you will not reload JavaScript and stylesheets. This definitely can make an app perform better, even if the JavaScript and stylesheets are cached by the browser, as they will still require parsing. ## Requirements for Using Turbolinks + 1. You are **not using [react-router](https://github.com/ReactTraining/react-router)** or you are prepared to deal with some potential issues with where react-router and Turbolinks overlaps. 2. You are **using one JS and one CSS file** throughout your app. Otherwise, you will have to figure out how best to handle multiple JS and CSS files throughout the app given Turbolinks. ## Why Not Turbolinks -1. [react-router](https://github.com/ReactTraining/react-router) handles the back and forward buttons, as does TurboLinks. You *might* be able to make this work. *Please share your findings.* + +1. [react-router](https://github.com/ReactTraining/react-router) handles the back and forward buttons, as does TurboLinks. You _might_ be able to make this work. _Please share your findings._ 1. You want to do code splitting to minimize the JavaScript loaded. ## More Information -* CSRF tokens need thorough checking with Turbolinks5. Turbolinks5 changes the head element by JavaScript (not only body) on page changes with the correct csrf meta tag, but if the JS code parsed this from head when several windows were opened, then our specs were not all passing. I didn't look details however, may be it is app code related, not library code. Anyway it may need additional check because there is CSRF helper in ReactOnRails and it need to work with Turbolinks5. -* Turbolinks5 send requests without the `Accept: */*` in the header, only exactly like `Accept: text/html` which makes Rails behave a bit specifically compared to normal and mime-parsing, which is skipped by when Rails see */*. For some more details on Rails and */* can read [Mime Type Resolution in Rails](http://blog.bigbinary.com/2010/11/23/mime-type-resolution-in-rails.html) -* If you're using multiple Webpack bundles, be sure to ensure that there are no name conflicts between JS objects or redux store paths. + +- CSRF tokens need thorough checking with Turbolinks5. Turbolinks5 changes the head element by JavaScript (not only body) on page changes with the correct csrf meta tag, but if the JS code parsed this from head when several windows were opened, then our specs were not all passing. I didn't look details however, may be it is app code related, not library code. Anyway it may need additional check because there is CSRF helper in ReactOnRails and it need to work with Turbolinks5. +- Turbolinks5 sends requests without the `Accept: */*` in the header, only exactly like `Accept: text/html` which makes Rails behave a bit specifically compared to normal and mime-parsing, which is skipped by when Rails sees `*/*`. For some more details on Rails and `*/*` you can read [Mime Type Resolution in Rails](http://blog.bigbinary.com/2010/11/23/mime-type-resolution-in-rails.html). +- If you're using multiple Webpack bundles, be sure to ensure that there are no name conflicts between JS objects or redux store paths. ### Install Checklist + 1. Include turbolinks via yarn as shown in the [react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/blob/8a6c8aa2e3b7ae5b08b0a9744fb3a63a2fe0f002/client/webpack.client.base.config.js#L22) or include the gem "turbolinks". 1. Included the proper "track" tags when you include the javascript and stylesheet: - ```erb - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => 'reload' %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %> - ``` - NOTE: for Turbolinks 2.x, use 'data-turbolinks-track' => true + +```erb + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %> +``` + +NOTE: for Turbolinks 2.x, use `'data-turbolinks-track' => true` + 1. Add turbolinks to your `application.js` file: ```javascript //= require turbolinks ``` ## Turbolinks 5 + Turbolinks 5 is now being supported. React on Rails will automatically detect which version of Turbolinks you are using and use the correct event handlers. For more information on Turbolinks 5: [https://github.com/turbolinks/turbolinks](https://github.com/turbolinks/turbolinks) @@ -62,46 +73,55 @@ For more information on Turbolinks 5: [https://github.com/turbolinks/turbolinks] See the [instructions on installing from NPM](https://github.com/turbolinks/turbolinks#installation-using-npm). ```js -import Turbolinks from "turbolinks"; +import Turbolinks from 'turbolinks'; Turbolinks.start(); ``` -### async script loading -Generally async script loading can be done like: +### Async script loading + +Async script loading can be done like this: + ```erb <%= javascript_include_tag 'application', async: Rails.env.production? %> ``` -If you use ```document.addEventListener("turbolinks:load", function() {...});``` somewhere in your code, you will notice, that Turbolinks 5 does not fire ```turbolinks:load``` on initial page load. A quick workaround is to use ```defer``` instead of ```async```: + +If you use `document.addEventListener("turbolinks:load", function() {...});` somewhere in your code, you will notice, that Turbolinks 5 does not fire `turbolinks:load` on initial page load. A quick workaround is to use `defer` instead of `async`: + ```erb <%= javascript_include_tag 'application', defer: Rails.env.production? %> ``` + More information on this issue can be found here: https://github.com/turbolinks/turbolinks/issues/28 -When loading your scripts asynchronously you may experience, that your Components are not registered correctly. Call ```ReactOnRails.reactOnRailsPageLoaded()``` to re-initialize like so: -``` - document.addEventListener("turbolinks:load", function() { - ReactOnRails.reactOnRailsPageLoaded(); - }); +When loading your scripts asynchronously your components may not be registered correctly. Call `ReactOnRails.reactOnRailsPageLoaded()` to re-initialize like so: + +```js +document.addEventListener('turbolinks:load', function () { + ReactOnRails.reactOnRailsPageLoaded(); +}); ``` ## Troubleshooting + To turn on tracing of Turbolinks events, put this in your registration file, where you register your components. ```js - ReactOnRails.setOptions({ - traceTurbolinks: true, - turbo: true, - }); +ReactOnRails.setOptions({ + traceTurbolinks: true, + turbo: true, +}); ``` Rather than setting the value to true, you could set it to TRACE_TURBOLINKS, and then you could place this in your `webpack.client.base.config.js`: Define this const at the top of the file: + ```js - const devBuild = process.env.NODE_ENV !== 'production'; +const devBuild = process.env.NODE_ENV !== 'production'; ``` Add this DefinePlugin option: + ```js plugins: [ new webpack.DefinePlugin({ @@ -114,13 +134,15 @@ At Webpack compile time, the value of devBuild is inserted into your file. Once you do that, you'll see messages prefixed with **TURBO:** like this in the browser console: Turbolinks Classic: -``` + +```text TURBO: WITH TURBOLINKS: document page:before-unload and page:change handlers installed. (program) TURBO: reactOnRailsPageLoaded ``` Turbolinks 5: -``` + +```text TURBO: WITH TURBOLINKS 5: document turbolinks:before-render and turbolinks:render handlers installed. (program) TURBO: reactOnRailsPageLoaded ``` diff --git a/docs/release-notes/15.0.0.md b/docs/release-notes/15.0.0.md index e807f9393..db32d42b6 100644 --- a/docs/release-notes/15.0.0.md +++ b/docs/release-notes/15.0.0.md @@ -3,14 +3,18 @@ ## Major Features ### 🚀 React Server Components Support + Experience the future of React with full RSC integration in your Rails apps: -- Seamlessly use React Server Components + +- Seamlessly use React Server Components - Reduce client bundle sizes - Enable powerful new patterns for data fetching - ⚡️ Requires React on Rails Pro - [See the full tutorial](https://www.shakacode.com/react-on-rails-pro/docs/react-server-components-tutorial) ### Improved Component Hydration + Major improvements to component and store hydration: + - Components and stores now hydrate immediately rather than waiting for page load - Enables faster hydration, especially beneficial for streamed pages - Components can hydrate before the page is fully streamed @@ -19,8 +23,12 @@ Major improvements to component and store hydration: ## Breaking Changes +Support for React 16 and 17 is dropped. + ### Component Hydration Changes + - The `defer_generated_component_packs` and `force_load` configurations now default to `false` and `true` respectively. This means components will hydrate early without waiting for the full page load. This improves performance by eliminating unnecessary delays in hydration. + - The previous need for deferring scripts to prevent race conditions has been eliminated due to improved hydration handling. Making scripts not defer is critical to execute the hydration scripts early before the page is fully loaded. - The `force_load` configuration make `react-on-rails` hydrate components immediately as soon as their server-rendered HTML reaches the client, without waiting for the full page load. - If you want to keep the previous behavior, you can set `defer_generated_component_packs: true` or `force_load: false` in your `config/initializers/react_on_rails.rb` file. @@ -28,13 +36,15 @@ Major improvements to component and store hydration: - Redux store support `force_load` option now and it uses `config.force_load` value as the default value. Which means that the redux store will hydrate immediately as soon as its server-side data reaches the client. You can override this behavior for individual redux stores by setting `force_load: false` in the `redux_store` helper. - `ReactOnRails.reactOnRailsPageLoaded()` is now an async function: + - If you are manually calling this function to ensure components are hydrated (e.g. with async script loading), you must now await the promise it returns: + ```js // Before ReactOnRails.reactOnRailsPageLoaded(); // Code expecting all components to be hydrated - - // After + + // After await ReactOnRails.reactOnRailsPageLoaded(); // Code expecting all components to be hydrated ``` @@ -55,6 +65,7 @@ If you have deferred Redux stores and components like this: ``` By default, React on Rails assumes components depend on all previously created stores. This means: + - Neither `ReduxApp` nor `ComponentWithNoStore` will hydrate until `SimpleStore` is hydrated - Since the store is deferred to the end of the page, both components are forced to wait unnecessarily diff --git a/docs/testimonials/resortpass.md b/docs/testimonials/resortpass.md index 1d19aabb2..95056309d 100644 --- a/docs/testimonials/resortpass.md +++ b/docs/testimonials/resortpass.md @@ -1,6 +1,6 @@ # ResortPass Testimonial, by Leora Juster, December 10, 2018 -Many can write code that "works." Even fewer can write sophisticated code that both works and reflects a deep understanding of the technologies and paradigms involved. Only a select few can do the aforementioned while assisting in managing the expectations and time constraints of less technically informed members of software product teams to make the best design decisions possible. Justin and his team were instrumental in assisting us in setting design foundations and standards for our transition to a react on rails application. Just three months of work with the team at Shaka code and we have a main page of our application server-side rendering at exponentially improved speeds. The code and CSS files are well-organized and contain repeatable patterns easy to understand, allowing my team to build on what has already been accomplished. I learned a great deal from my interactions with Justin and his team, as they are just as great teachers as they are developers, and feel like I get to continually learn from them as I build on top of their code. Their different support and pro plan options make it easy to build a continuous professional relationship despite fluctuations in my team's funding, and their team is always extremely personable, punctual, and professional. +Many can write code that "works." Even fewer can write sophisticated code that both works and reflects a deep understanding of the technologies and paradigms involved. Only a select few can do the aforementioned while assisting in managing the expectations and time constraints of less technically informed members of software product teams to make the best design decisions possible. Justin and his team were instrumental in assisting us in setting design foundations and standards for our transition to a react on rails application. Just three months of work with the team at Shaka code and we have a main page of our application server-side rendering at exponentially improved speeds. The code and CSS files are well-organized and contain repeatable patterns easy to understand, allowing my team to build on what has already been accomplished. I learned a great deal from my interactions with Justin and his team, as they are just as great teachers as they are developers, and feel like I get to continually learn from them as I build on top of their code. Their different support and pro plan options make it easy to build a continuous professional relationship despite fluctuations in my team's funding, and their team is always extremely personable, punctual, and professional. Leora Juster, Full-Stack Lead Software Developer diff --git a/docs/testimonials/testimonials.md b/docs/testimonials/testimonials.md index e671f5793..a5c72d108 100644 --- a/docs/testimonials/testimonials.md +++ b/docs/testimonials/testimonials.md @@ -1,4 +1,5 @@ # Testimonials + # [HVMN Testimonial, Written by Paul Benigeri, October 12, 2018](https://www.shakacode.com/react-on-rails/docs/testimonials/hvmn/) > The price we paid for the consultation + the React on Rails pro license has already been made back a couple of times from hosting fees alone. The entire process was super hands off, and our core team was able to focus on shipping new feature during that sprint. diff --git a/knip.ts b/knip.ts index 4998eca79..8f1283cc4 100644 --- a/knip.ts +++ b/knip.ts @@ -15,9 +15,7 @@ const config: KnipConfig = { babel: { config: ['node_package/babel.config.js'], }, - ignore: [ - 'node_package/tests/emptyForTesting.js', - ], + ignore: ['node_package/tests/emptyForTesting.js'], ignoreBinaries: [ // Knip fails to detect it's declared in devDependencies 'nps', @@ -27,8 +25,6 @@ const config: KnipConfig = { ignoreDependencies: [ // Required for TypeScript compilation, but we don't depend on Turbolinks itself. '@types/turbolinks', - // used in package-scripts.yml - 'concurrently', // The Knip ESLint plugin fails to detect these are transitively required by a config, // though we don't actually use its rules anywhere. 'eslint-plugin-jsx-a11y', diff --git a/lib/generators/react_on_rails/templates/.eslintrc b/lib/generators/react_on_rails/templates/.eslintrc index ae94f82aa..95434d058 100644 --- a/lib/generators/react_on_rails/templates/.eslintrc +++ b/lib/generators/react_on_rails/templates/.eslintrc @@ -1,5 +1,5 @@ --- -extends: +extends: - eslint-config-shakacode - prettier diff --git a/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css b/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css index d2388c7c3..1983caaa8 100644 --- a/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +++ b/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css @@ -1,4 +1,4 @@ .bright { - color: green; - font-weight: bold; + color: green; + font-weight: bold; } diff --git a/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml b/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml index 2ac742719..4f1a72d7f 100644 --- a/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +++ b/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml @@ -37,7 +37,7 @@ development: # port: 8080 compress: true # Note that apps that do not check the host are vulnerable to DNS rebinding attacks - allowed_hosts: [ 'localhost' ] + allowed_hosts: ['localhost'] pretty: true headers: 'Access-Control-Allow-Origin': '*' diff --git a/node_package/src/Authenticity.ts b/node_package/src/Authenticity.ts index 82c7484ae..593af3ad6 100644 --- a/node_package/src/Authenticity.ts +++ b/node_package/src/Authenticity.ts @@ -9,7 +9,7 @@ export default { return null; }, - authenticityHeaders(otherHeaders: {[id: string]: string} = {}): AuthenticityHeaders { + authenticityHeaders(otherHeaders: { [id: string]: string } = {}): AuthenticityHeaders { return Object.assign(otherHeaders, { 'X-CSRF-Token': this.authenticityToken(), 'X-Requested-With': 'XMLHttpRequest', diff --git a/node_package/src/CallbackRegistry.ts b/node_package/src/CallbackRegistry.ts index 93f8e07e4..988430275 100644 --- a/node_package/src/CallbackRegistry.ts +++ b/node_package/src/CallbackRegistry.ts @@ -1,6 +1,6 @@ -import { ItemRegistrationCallback } from "./types"; -import { onPageLoaded, onPageUnloaded } from "./pageLifecycle"; -import { getContextAndRailsContext } from "./context"; +import { ItemRegistrationCallback } from './types'; +import { onPageLoaded, onPageUnloaded } from './pageLifecycle'; +import { getContextAndRailsContext } from './context'; /** * Represents information about a registered item including its value, @@ -36,7 +36,9 @@ export default class CallbackRegistry { waitingPromiseInfo.reject(this.createNotFoundError(itemName)); }); this.notUsedItems.forEach((itemName) => { - console.warn(`Warning: ${this.registryType} '${itemName}' was registered but never used. This may indicate unused code that can be removed.`); + console.warn( + `Warning: ${this.registryType} '${itemName}' was registered but never used. This may indicate unused code that can be removed.`, + ); }); }; @@ -94,7 +96,7 @@ export default class CallbackRegistry { this.initializeTimeoutEvents(); try { return this.get(name); - } catch(error) { + } catch (error) { if (this.timedout) { throw error; } @@ -119,8 +121,8 @@ export default class CallbackRegistry { const keys = Array.from(this.registeredItems.keys()).join(', '); return new Error( `Could not find ${this.registryType} registered with name ${itemName}. ` + - `Registered ${this.registryType} names include [ ${keys} ]. ` + - `Maybe you forgot to register the ${this.registryType}?` + `Registered ${this.registryType} names include [ ${keys} ]. ` + + `Maybe you forgot to register the ${this.registryType}?`, ); } } diff --git a/node_package/src/ClientSideRenderer.ts b/node_package/src/ClientSideRenderer.ts index 212964cb5..5379d2a6e 100644 --- a/node_package/src/ClientSideRenderer.ts +++ b/node_package/src/ClientSideRenderer.ts @@ -1,17 +1,11 @@ -import * as ReactDOM from 'react-dom'; import type { ReactElement } from 'react'; -import type { - RailsContext, - RegisteredComponent, - RenderFunction, - Root, -} from './types'; +import type { Root } from 'react-dom/client'; +import type { RailsContext, RegisteredComponent, RenderFunction } from './types'; import { getContextAndRailsContext, resetContextAndRailsContext, type Context } from './context'; import createReactOutput from './createReactOutput'; import { isServerRenderHash } from './isServerRenderResult'; import reactHydrateOrRender from './reactHydrateOrRender'; -import { supportsRootApi } from './reactApis'; import { debugTurbolinks } from './turbolinksUtils'; const REACT_ON_RAILS_STORE_ATTRIBUTE = 'data-js-react-on-rails-store'; @@ -27,9 +21,11 @@ function delegateToRenderer( if (isRenderer) { if (trace) { - console.log(`\ -DELEGATING TO RENDERER ${name} for dom node with id: ${domNodeId} with props, railsContext:`, - props, railsContext); + console.log( + `DELEGATING TO RENDERER ${name} for dom node with id: ${domNodeId} with props, railsContext:`, + props, + railsContext, + ); } (component as RenderFunction)(props, railsContext, domNodeId); @@ -39,7 +35,8 @@ DELEGATING TO RENDERER ${name} for dom node with id: ${domNodeId} with props, ra return false; } -const getDomId = (domIdOrElement: string | Element): string => typeof domIdOrElement === 'string' ? domIdOrElement : domIdOrElement.getAttribute('data-dom-id') || ''; +const getDomId = (domIdOrElement: string | Element): string => + typeof domIdOrElement === 'string' ? domIdOrElement : domIdOrElement.getAttribute('data-dom-id') || ''; class ComponentRenderer { private domNodeId: string; private state: 'unmounted' | 'rendering' | 'rendered'; @@ -50,22 +47,23 @@ class ComponentRenderer { const domId = getDomId(domIdOrElement); this.domNodeId = domId; this.state = 'rendering'; - const el = typeof domIdOrElement === 'string' ? document.querySelector(`[data-dom-id=${domId}]`) : domIdOrElement; + const el = + typeof domIdOrElement === 'string' ? document.querySelector(`[data-dom-id=${domId}]`) : domIdOrElement; if (!el) return; const storeDependencies = el.getAttribute('data-store-dependencies'); - const storeDependenciesArray = storeDependencies ? JSON.parse(storeDependencies) as string[] : []; + const storeDependenciesArray = storeDependencies ? (JSON.parse(storeDependencies) as string[]) : []; const { context, railsContext } = getContextAndRailsContext(); if (!context || !railsContext) return; // Wait for all store dependencies to be loaded this.renderPromise = Promise.all( - storeDependenciesArray.map(storeName => context.ReactOnRails.getOrWaitForStore(storeName)), + storeDependenciesArray.map((storeName) => context.ReactOnRails.getOrWaitForStore(storeName)), ).then(() => { - if (this.state === 'unmounted') return Promise.resolve(); - return this.render(el, context, railsContext); - }); + if (this.state === 'unmounted') return Promise.resolve(); + return this.render(el, context, railsContext); + }); } /** @@ -76,7 +74,7 @@ class ComponentRenderer { // This must match lib/react_on_rails/helper.rb const name = el.getAttribute('data-component-name') || ''; const { domNodeId } = this; - const props = (el.textContent !== null) ? JSON.parse(el.textContent) : {}; + const props = el.textContent !== null ? JSON.parse(el.textContent) : {}; const trace = el.getAttribute('data-trace') === 'true'; try { @@ -91,9 +89,8 @@ class ComponentRenderer { return; } - // Hydrate if available and was server rendered - // @ts-expect-error potentially present if React 18 or greater - const shouldHydrate = !!(ReactDOM.hydrate || ReactDOM.hydrateRoot) && !!domNode.innerHTML; + // Hydrate if the node was server rendered + const shouldHydrate = !!domNode.innerHTML; const reactElementOrRouterResult = createReactOutput({ componentObj, @@ -106,20 +103,21 @@ class ComponentRenderer { if (isServerRenderHash(reactElementOrRouterResult)) { throw new Error(`\ - You returned a server side type of react-router error: ${JSON.stringify(reactElementOrRouterResult)} - You should return a React.Component always for the client side entry point.`); +You returned a server side type of react-router error: ${JSON.stringify(reactElementOrRouterResult)} +You should return a React.Component always for the client side entry point.`); } else { - const rootOrElement = reactHydrateOrRender(domNode, reactElementOrRouterResult as ReactElement, shouldHydrate); + this.root = reactHydrateOrRender( + domNode, + reactElementOrRouterResult as ReactElement, + shouldHydrate, + ); this.state = 'rendered'; - if (supportsRootApi) { - this.root = rootOrElement as Root; - } } } } catch (e: unknown) { const error = e instanceof Error ? e : new Error(e?.toString() ?? 'Unknown error'); console.error(error.message); - error.message = `ReactOnRails encountered an error while rendering component: ${name}. See above error message.` + error.message = `ReactOnRails encountered an error while rendering component: ${name}. See above error message.`; throw error; } } @@ -131,23 +129,8 @@ class ComponentRenderer { } this.state = 'unmounted'; - if (supportsRootApi) { - this.root?.unmount(); - this.root = undefined; - } else { - const domNode = document.getElementById(this.domNodeId); - if (!domNode) { - return; - } - - try { - ReactDOM.unmountComponentAtNode(domNode); - } catch (e: unknown) { - const error = e instanceof Error ? e : new Error('Unknown error'); - console.info(`Caught error calling unmountComponentAtNode: ${error.message} for domNode`, - domNode, error); - } - } + this.root?.unmount(); + this.root = undefined; } waitUntilRendered(): Promise { @@ -170,11 +153,16 @@ class StoreRenderer { } const name = storeDataElement.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || ''; - const props = (storeDataElement.textContent !== null) ? JSON.parse(storeDataElement.textContent) : {}; + const props = storeDataElement.textContent !== null ? JSON.parse(storeDataElement.textContent) : {}; this.hydratePromise = this.hydrate(context, railsContext, name, props); } - private async hydrate(context: Context, railsContext: RailsContext, name: string, props: Record) { + private async hydrate( + context: Context, + railsContext: RailsContext, + name: string, + props: Record, + ) { const storeGenerator = await context.ReactOnRails.getOrWaitForStoreGenerator(name); if (this.state === 'unmounted') { return; @@ -229,10 +217,16 @@ function unmountAllComponents(): void { const storeRenderers = new Map(); export async function hydrateStore(storeNameOrElement: string | Element) { - const storeName = typeof storeNameOrElement === 'string' ? storeNameOrElement : storeNameOrElement.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || ''; + const storeName = + typeof storeNameOrElement === 'string' + ? storeNameOrElement + : storeNameOrElement.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || ''; let storeRenderer = storeRenderers.get(storeName); if (!storeRenderer) { - const storeDataElement = typeof storeNameOrElement === 'string' ? document.querySelector(`[${REACT_ON_RAILS_STORE_ATTRIBUTE}="${storeNameOrElement}"]`) : storeNameOrElement; + const storeDataElement = + typeof storeNameOrElement === 'string' + ? document.querySelector(`[${REACT_ON_RAILS_STORE_ATTRIBUTE}="${storeNameOrElement}"]`) + : storeNameOrElement; if (!storeDataElement) { return; } diff --git a/node_package/src/ComponentRegistry.ts b/node_package/src/ComponentRegistry.ts index dc86b6ee3..5a2d153bc 100644 --- a/node_package/src/ComponentRegistry.ts +++ b/node_package/src/ComponentRegistry.ts @@ -1,8 +1,4 @@ -import { - type RegisteredComponent, - type ReactComponentOrRenderFunction, - type RenderFunction, -} from './types'; +import { type RegisteredComponent, type ReactComponentOrRenderFunction, type RenderFunction } from './types'; import isRenderFunction from './isRenderFunction'; import CallbackRegistry from './CallbackRegistry'; @@ -13,7 +9,7 @@ export default { * @param components { component1: component1, component2: component2, etc. } */ register(components: { [id: string]: ReactComponentOrRenderFunction }): void { - Object.keys(components).forEach(name => { + Object.keys(components).forEach((name) => { if (componentRegistry.has(name)) { console.warn('Called register for component that is already registered', name); } diff --git a/node_package/src/RSCClientRoot.ts b/node_package/src/RSCClientRoot.ts index 5117ae58c..eb2820267 100644 --- a/node_package/src/RSCClientRoot.ts +++ b/node_package/src/RSCClientRoot.ts @@ -1,4 +1,4 @@ -"use client"; +'use client'; import * as React from 'react'; import * as ReactDOMClient from 'react-dom/client'; @@ -17,7 +17,7 @@ export type RSCClientRootProps = { componentName: string; rscPayloadGenerationUrlPath: string; componentProps?: unknown; -} +}; const createFromFetch = async (fetchPromise: Promise) => { const response = await fetchPromise; @@ -27,13 +27,13 @@ const createFromFetch = async (fetchPromise: Promise) => { } const transformedStream = transformRSCStreamAndReplayConsoleLogs(stream); return createFromReadableStream(transformedStream); -} +}; const fetchRSC = ({ componentName, rscPayloadGenerationUrlPath, componentProps }: RSCClientRootProps) => { const propsString = JSON.stringify(componentProps); const strippedUrlPath = rscPayloadGenerationUrlPath.replace(/^\/|\/$/g, ''); return createFromFetch(fetch(`/${strippedUrlPath}/${componentName}?props=${propsString}`)); -} +}; /** * RSCClientRoot is a React component that handles client-side rendering of React Server Components (RSC). @@ -48,12 +48,12 @@ const fetchRSC = ({ componentName, rscPayloadGenerationUrlPath, componentProps } * @requires React 19+ * @requires react-on-rails-rsc */ -const RSCClientRoot: RenderFunction = async ({ - componentName, - rscPayloadGenerationUrlPath, - componentProps, -}: RSCClientRootProps, _railsContext?: RailsContext, domNodeId?: string) => { - const root = await fetchRSC({ componentName, rscPayloadGenerationUrlPath, componentProps }) +const RSCClientRoot: RenderFunction = async ( + { componentName, rscPayloadGenerationUrlPath, componentProps }: RSCClientRootProps, + _railsContext?: RailsContext, + domNodeId?: string, +) => { + const root = await fetchRSC({ componentName, rscPayloadGenerationUrlPath, componentProps }); if (!domNodeId) { throw new Error('RSCClientRoot: No domNodeId provided'); } @@ -70,6 +70,6 @@ const RSCClientRoot: RenderFunction = async ({ // However, the returned value of renderFunction is not used in ReactOnRails // TODO: fix this behavior return ''; -} +}; export default RSCClientRoot; diff --git a/node_package/src/ReactOnRails.client.ts b/node_package/src/ReactOnRails.client.ts index 24a45fc9a..2403a2ef6 100644 --- a/node_package/src/ReactOnRails.client.ts +++ b/node_package/src/ReactOnRails.client.ts @@ -1,4 +1,5 @@ import type { ReactElement } from 'react'; +import type { Root } from 'react-dom/client'; import * as ClientStartup from './clientStartup'; import { renderOrHydrateComponent, hydrateStore } from './ClientSideRenderer'; import ComponentRegistry from './ComponentRegistry'; @@ -10,7 +11,6 @@ import context from './context'; import type { RegisteredComponent, RenderResult, - RenderReturnType, ReactComponentOrRenderFunction, AuthenticityHeaders, Store, @@ -62,8 +62,10 @@ ctx.ReactOnRails = { */ registerStoreGenerators(storeGenerators: { [id: string]: StoreGenerator }): void { if (!storeGenerators) { - throw new Error('Called ReactOnRails.registerStoreGenerators with a null or undefined, rather than ' + - 'an Object with keys being the store names and the values are the store generators.'); + throw new Error( + 'Called ReactOnRails.registerStoreGenerators with a null or undefined, rather than ' + + 'an Object with keys being the store names and the values are the store generators.', + ); } StoreRegistry.register(storeGenerators); @@ -101,13 +103,13 @@ ctx.ReactOnRails = { }, /** - * Renders or hydrates the React element passed. In case React version is >=18 will use the root API. + * Renders or hydrates the React element passed. * @param domNode * @param reactElement * @param hydrate if true will perform hydration, if false will render - * @returns {Root|ReactComponent|ReactElement|null} + * @returns {Root} */ - reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType { + reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): Root { return reactHydrateOrRender(domNode, reactElement, hydrate); }, @@ -117,7 +119,7 @@ ctx.ReactOnRails = { * `traceTurbolinks: true|false Gives you debugging messages on Turbolinks events * `turbo: true|false Turbo (the follower of Turbolinks) events will be registered, if set to true. */ - setOptions(newOptions: {traceTurbolinks?: boolean, turbo?: boolean }): void { + setOptions(newOptions: { traceTurbolinks?: boolean; turbo?: boolean }): void { if (typeof newOptions.traceTurbolinks !== 'undefined') { this.options.traceTurbolinks = newOptions.traceTurbolinks; @@ -133,9 +135,7 @@ ctx.ReactOnRails = { } if (Object.keys(newOptions).length > 0) { - throw new Error( - `Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`, - ); + throw new Error(`Invalid options passed to ReactOnRails.options: ${JSON.stringify(newOptions)}`); } }, @@ -222,30 +222,27 @@ ctx.ReactOnRails = { * * Does this: * ```js - * ReactDOM.render(React.createElement(HelloWorldApp, {name: "Stranger"}), - * document.getElementById('app')) - * ``` - * under React 16/17 and - * ```js * const root = ReactDOMClient.createRoot(document.getElementById('app')) - * root.render(React.createElement(HelloWorldApp, {name: "Stranger"})) + * root.render(React.createElement(HelloWorldApp, {name: 'Stranger'})) * return root * ``` - * under React 18+. * * @param name Name of your registered component * @param props Props to pass to your component * @param domNodeId - * @param hydrate Pass truthy to update server rendered html. Default is falsy - * @returns {Root|ReactComponent|ReactElement} Under React 18+: the created React root + * @param hydrate Pass truthy to update server rendered HTML. Default is falsy + * @returns {Root} The created React root * (see "What is a root?" in https://github.com/reactwg/react-18/discussions/5). - * Under React 16/17: Reference to your component's backing instance or `null` for stateless components. */ - render(name: string, props: Record, domNodeId: string, hydrate: boolean): RenderReturnType { + render(name: string, props: Record, domNodeId: string, hydrate: boolean): Root { const componentObj = ComponentRegistry.get(name); const reactElement = createReactOutput({ componentObj, props, domNodeId }); - return reactHydrateOrRender(document.getElementById(domNodeId) as Element, reactElement as ReactElement, hydrate); + return reactHydrateOrRender( + document.getElementById(domNodeId) as Element, + reactElement as ReactElement, + hydrate, + ); }, /** @@ -271,7 +268,9 @@ ctx.ReactOnRails = { * @param options */ serverRenderReactComponent(): null | string | Promise { - throw new Error('serverRenderReactComponent is not available in "react-on-rails/client". Import "react-on-rails" server-side.'); + throw new Error( + 'serverRenderReactComponent is not available in "react-on-rails/client". Import "react-on-rails" server-side.', + ); }, /** @@ -279,7 +278,9 @@ ctx.ReactOnRails = { * @param options */ streamServerRenderedReactComponent() { - throw new Error('streamServerRenderedReactComponent is only supported when using a bundle built for Node.js environments'); + throw new Error( + 'streamServerRenderedReactComponent is only supported when using a bundle built for Node.js environments', + ); }, /** @@ -294,7 +295,9 @@ ctx.ReactOnRails = { * @param options */ handleError(): string | undefined { - throw new Error('handleError is not available in "react-on-rails/client". Import "react-on-rails" server-side.'); + throw new Error( + 'handleError is not available in "react-on-rails/client". Import "react-on-rails" server-side.', + ); }, /** @@ -337,5 +340,5 @@ ctx.ReactOnRails.resetOptions(); ClientStartup.clientStartup(ctx); -export * from "./types"; +export * from './types'; export default ctx.ReactOnRails; diff --git a/node_package/src/ReactOnRails.full.ts b/node_package/src/ReactOnRails.full.ts index c80c8dd5b..2a5f0e856 100644 --- a/node_package/src/ReactOnRails.full.ts +++ b/node_package/src/ReactOnRails.full.ts @@ -1,15 +1,13 @@ import handleError from './handleError'; import serverRenderReactComponent from './serverRenderReactComponent'; -import type { - RenderParams, - RenderResult, - ErrorOptions, -} from './types'; +import type { RenderParams, RenderResult, ErrorOptions } from './types'; import Client from './ReactOnRails.client'; if (typeof window !== 'undefined') { - console.log('Optimization opportunity: "react-on-rails" includes ~14KB of server-rendering code. Browsers may not need it. See https://forum.shakacode.com/t/how-to-use-different-versions-of-a-file-for-client-and-server-rendering/1352 (Requires creating a free account)'); + console.log( + 'Optimization opportunity: "react-on-rails" includes ~14KB of server-rendering code. Browsers may not need it. See https://forum.shakacode.com/t/how-to-use-different-versions-of-a-file-for-client-and-server-rendering/1352 (Requires creating a free account)', + ); } /** @@ -22,7 +20,8 @@ Client.handleError = (options: ErrorOptions): string | undefined => handleError( * Used by server rendering by Rails * @param options */ -Client.serverRenderReactComponent = (options: RenderParams): null | string | Promise => serverRenderReactComponent(options); +Client.serverRenderReactComponent = (options: RenderParams): null | string | Promise => + serverRenderReactComponent(options); -export * from "./types"; +export * from './types'; export default Client; diff --git a/node_package/src/ReactOnRailsRSC.ts b/node_package/src/ReactOnRailsRSC.ts index fc3ad516d..3b3d7e7d3 100644 --- a/node_package/src/ReactOnRailsRSC.ts +++ b/node_package/src/ReactOnRailsRSC.ts @@ -6,10 +6,7 @@ import { RSCRenderParams, StreamRenderState } from './types'; import ReactOnRails from './ReactOnRails.full'; import buildConsoleReplay from './buildConsoleReplay'; import handleError from './handleError'; -import { - convertToError, - createResultObject, -} from './serverRenderUtils'; +import { convertToError, createResultObject } from './serverRenderUtils'; import { streamServerRenderedComponent, @@ -29,35 +26,34 @@ const streamRenderRSCComponent = (reactElement: ReactElement, options: RSCRender const renderState: StreamRenderState = { result: null, hasErrors: false, - isShellReady: true + isShellReady: true, }; - const { pipeToTransform, readableStream, emitError } = transformRenderStreamChunksToResultObject(renderState); - loadReactClientManifest(reactClientManifestFileName).then((reactClientManifest) => { - const rscStream = renderToPipeableStream( - reactElement, - reactClientManifest, - { + const { pipeToTransform, readableStream, emitError } = + transformRenderStreamChunksToResultObject(renderState); + loadReactClientManifest(reactClientManifestFileName) + .then((reactClientManifest) => { + const rscStream = renderToPipeableStream(reactElement, reactClientManifest, { onError: (err) => { const error = convertToError(err); - console.error("Error in RSC stream", error); + console.error('Error in RSC stream', error); if (throwJsErrors) { emitError(error); } renderState.hasErrors = true; renderState.error = error; - } - } - ); - pipeToTransform(rscStream); - }).catch((e) => { - const error = convertToError(e); - renderState.hasErrors = true; - renderState.error = error; - const htmlResult = handleError({ e: error, name: options.name, serverSide: true }); - const jsonResult = JSON.stringify(createResultObject(htmlResult, buildConsoleReplay(), renderState)); - return stringToStream(jsonResult); - }); + }, + }); + pipeToTransform(rscStream); + }) + .catch((e) => { + const error = convertToError(e); + renderState.hasErrors = true; + renderState.error = error; + const htmlResult = handleError({ e: error, name: options.name, serverSide: true }); + const jsonResult = JSON.stringify(createResultObject(htmlResult, buildConsoleReplay(), renderState)); + return stringToStream(jsonResult); + }); return readableStream; }; diff --git a/node_package/src/StoreRegistry.ts b/node_package/src/StoreRegistry.ts index 6bb5311e4..19f094c77 100644 --- a/node_package/src/StoreRegistry.ts +++ b/node_package/src/StoreRegistry.ts @@ -10,15 +10,17 @@ export default { * @param storeGenerators { name1: storeGenerator1, name2: storeGenerator2 } */ register(storeGenerators: { [id: string]: StoreGenerator }): void { - Object.keys(storeGenerators).forEach(name => { + Object.keys(storeGenerators).forEach((name) => { if (storeGeneratorRegistry.has(name)) { console.warn('Called registerStore for store that is already registered', name); } const store = storeGenerators[name]; if (!store) { - throw new Error('Called ReactOnRails.registerStores with a null or undefined as a value ' + - `for the store generator with key ${name}.`); + throw new Error( + 'Called ReactOnRails.registerStores with a null or undefined as a value ' + + `for the store generator with key ${name}.`, + ); } storeGeneratorRegistry.set(name, store); @@ -37,8 +39,7 @@ export default { return hydratedStoreRegistry.get(name); } catch (error) { if (hydratedStoreRegistry.getAll().size === 0) { - const msg = -`There are no stores hydrated and you are requesting the store ${name}. + const msg = `There are no stores hydrated and you are requesting the store ${name}. This can happen if you are server rendering and either: 1. You do not call redux_store near the top of your controller action's view (not the layout) and before any call to react_component. diff --git a/node_package/src/buildConsoleReplay.ts b/node_package/src/buildConsoleReplay.ts index 91fe377c3..226979868 100644 --- a/node_package/src/buildConsoleReplay.ts +++ b/node_package/src/buildConsoleReplay.ts @@ -4,22 +4,26 @@ import scriptSanitizedVal from './scriptSanitizedVal'; declare global { interface Console { history?: { - arguments: Array>; level: "error" | "log" | "debug"; + arguments: Array>; + level: 'error' | 'log' | 'debug'; }[]; } } /** @internal Exported only for tests */ -export function consoleReplay(customConsoleHistory: typeof console['history'] | undefined = undefined, numberOfMessagesToSkip: number = 0): string { +export function consoleReplay( + customConsoleHistory: (typeof console)['history'] | undefined = undefined, + numberOfMessagesToSkip: number = 0, +): string { // console.history is a global polyfill used in server rendering. const consoleHistory = customConsoleHistory ?? console.history; - if (!(Array.isArray(consoleHistory))) { + if (!Array.isArray(consoleHistory)) { return ''; } - const lines = consoleHistory.slice(numberOfMessagesToSkip).map(msg => { - const stringifiedList = msg.arguments.map(arg => { + const lines = consoleHistory.slice(numberOfMessagesToSkip).map((msg) => { + const stringifiedList = msg.arguments.map((arg) => { let val: string; try { if (typeof arg === 'string') { @@ -45,6 +49,12 @@ export function consoleReplay(customConsoleHistory: typeof console['history'] | return lines.join('\n'); } -export default function buildConsoleReplay(customConsoleHistory: typeof console['history'] | undefined = undefined, numberOfMessagesToSkip: number = 0): string { - return RenderUtils.wrapInScriptTags('consoleReplayLog', consoleReplay(customConsoleHistory, numberOfMessagesToSkip)); +export default function buildConsoleReplay( + customConsoleHistory: (typeof console)['history'] | undefined = undefined, + numberOfMessagesToSkip: number = 0, +): string { + return RenderUtils.wrapInScriptTags( + 'consoleReplayLog', + consoleReplay(customConsoleHistory, numberOfMessagesToSkip), + ); } diff --git a/node_package/src/clientStartup.ts b/node_package/src/clientStartup.ts index f83393ece..ccdc77358 100644 --- a/node_package/src/clientStartup.ts +++ b/node_package/src/clientStartup.ts @@ -11,10 +11,7 @@ import { debugTurbolinks } from './turbolinksUtils'; export async function reactOnRailsPageLoaded() { debugTurbolinks('reactOnRailsPageLoaded'); - await Promise.all([ - hydrateAllStores(), - renderOrHydrateAllComponents(), - ]); + await Promise.all([hydrateAllStores(), renderOrHydrateAllComponents()]); } function reactOnRailsPageUnloaded(): void { diff --git a/node_package/src/context.ts b/node_package/src/context.ts index 0a85b21a4..bd2251e62 100644 --- a/node_package/src/context.ts +++ b/node_package/src/context.ts @@ -19,9 +19,7 @@ export type Context = Window | typeof globalThis; * Get the context, be it window or global */ export default function context(this: void): Context | void { - return ((typeof window !== 'undefined') && window) || - ((typeof global !== 'undefined') && global) || - this; + return (typeof window !== 'undefined' && window) || (typeof global !== 'undefined' && global) || this; } export function isWindow(ctx: Context): ctx is Window { diff --git a/node_package/src/createReactOutput.ts b/node_package/src/createReactOutput.ts index 00194a7e9..888e67fa2 100644 --- a/node_package/src/createReactOutput.ts +++ b/node_package/src/createReactOutput.ts @@ -1,9 +1,14 @@ /* eslint-disable react/prop-types */ import * as React from 'react'; -import type { ServerRenderResult, - CreateParams, ReactComponent, RenderFunction, CreateReactOutputResult } from './types/index'; -import {isServerRenderHash, isPromise} from "./isServerRenderResult"; +import type { + ServerRenderResult, + CreateParams, + ReactComponent, + RenderFunction, + CreateReactOutputResult, +} from './types/index'; +import { isServerRenderHash, isPromise } from './isServerRenderResult'; /** * Logic to either call the renderFunction or call React.createElement to get the @@ -30,11 +35,17 @@ export default function createReactOutput({ if (railsContext && railsContext.serverSide) { console.log(`RENDERED ${name} to dom node with id: ${domNodeId}`); } else if (shouldHydrate) { - console.log(`HYDRATED ${name} in dom node with id: ${domNodeId} using props, railsContext:`, - props, railsContext); + console.log( + `HYDRATED ${name} in dom node with id: ${domNodeId} using props, railsContext:`, + props, + railsContext, + ); } else { - console.log(`RENDERED ${name} to dom node with id: ${domNodeId} with props, railsContext:`, - props, railsContext); + console.log( + `RENDERED ${name} to dom node with id: ${domNodeId} with props, railsContext:`, + props, + railsContext, + ); } } @@ -47,22 +58,23 @@ export default function createReactOutput({ if (isServerRenderHash(renderFunctionResult as CreateReactOutputResult)) { // We just return at this point, because calling function knows how to handle this case and // we can't call React.createElement with this type of Object. - return (renderFunctionResult as ServerRenderResult); + return renderFunctionResult as ServerRenderResult; } if (isPromise(renderFunctionResult as CreateReactOutputResult)) { // We just return at this point, because calling function knows how to handle this case and // we can't call React.createElement with this type of Object. - return (renderFunctionResult as Promise); + return renderFunctionResult as Promise; } if (React.isValidElement(renderFunctionResult)) { // If already a ReactElement, then just return it. console.error( -`Warning: ReactOnRails: Your registered render-function (ReactOnRails.register) for ${name} + `Warning: ReactOnRails: Your registered render-function (ReactOnRails.register) for ${name} incorrectly returned a React Element (JSX). Instead, return a React Function Component by wrapping your JSX in a function. ReactOnRails v13 will throw error on this, as React Hooks do not -work if you return JSX. Update by wrapping the result JSX of ${name} in a fat arrow function.`); +work if you return JSX. Update by wrapping the result JSX of ${name} in a fat arrow function.`, + ); return renderFunctionResult; } diff --git a/node_package/src/handleError.ts b/node_package/src/handleError.ts index f9b615a64..4c9f346a6 100644 --- a/node_package/src/handleError.ts +++ b/node_package/src/handleError.ts @@ -12,18 +12,16 @@ function handleRenderFunctionIssue(options: ErrorOptions): string { 'A Render-Function takes a single arg of props (and the location for react-router) ' + 'and returns a ReactElement.'; - let shouldBeRenderFunctionError = - `ERROR: ReactOnRails is incorrectly detecting Render-Function to be false. The React -component '${name}' seems to be a Render-Function.\n${lastLine}`; + let shouldBeRenderFunctionError = `ERROR: ReactOnRails is incorrectly detecting Render-Function to be false. \ +The React component '${name}' seems to be a Render-Function.\n${lastLine}`; const reMatchShouldBeGeneratorError = /Can't add property context, object is not extensible/; if (reMatchShouldBeGeneratorError.test(e.message)) { msg += `${shouldBeRenderFunctionError}\n\n`; console.error(shouldBeRenderFunctionError); } - shouldBeRenderFunctionError = - `ERROR: ReactOnRails is incorrectly detecting renderFunction to be true, but the React -component '${name}' is not a Render-Function.\n${lastLine}`; + shouldBeRenderFunctionError = `ERROR: ReactOnRails is incorrectly detecting renderFunction to be true, \ +but the React component '${name}' is not a Render-Function.\n${lastLine}`; const reMatchShouldNotBeGeneratorError = /Cannot call a class as a function/; @@ -65,7 +63,7 @@ ${e.stack}`; return ReactDOMServer.renderToString(reactElement); } - return "undefined"; + return 'undefined'; }; export default handleError; diff --git a/node_package/src/isRenderFunction.ts b/node_package/src/isRenderFunction.ts index 21c011fb7..22cb854f4 100644 --- a/node_package/src/isRenderFunction.ts +++ b/node_package/src/isRenderFunction.ts @@ -1,6 +1,6 @@ // See discussion: // https://discuss.reactjs.org/t/how-to-determine-if-js-object-is-react-component/2825/2 -import { ReactComponentOrRenderFunction, RenderFunction } from "./types/index"; +import { ReactComponentOrRenderFunction, RenderFunction } from './types/index'; /** * Used to determine we'll call be calling React.createElement on the component of if this is a @@ -8,7 +8,9 @@ import { ReactComponentOrRenderFunction, RenderFunction } from "./types/index"; * @param component * @returns {boolean} */ -export default function isRenderFunction(component: ReactComponentOrRenderFunction): component is RenderFunction { +export default function isRenderFunction( + component: ReactComponentOrRenderFunction, +): component is RenderFunction { // No for es5 or es6 React Component if ((component as RenderFunction).prototype?.isReactComponent) { return false; diff --git a/node_package/src/isServerRenderResult.ts b/node_package/src/isServerRenderResult.ts index 88f9cf5be..cf8772b1f 100644 --- a/node_package/src/isServerRenderResult.ts +++ b/node_package/src/isServerRenderResult.ts @@ -1,15 +1,16 @@ import type { CreateReactOutputResult, ServerRenderResult } from './types/index'; -export function isServerRenderHash(testValue: CreateReactOutputResult): - testValue is ServerRenderResult { +export function isServerRenderHash(testValue: CreateReactOutputResult): testValue is ServerRenderResult { return !!( (testValue as ServerRenderResult).renderedHtml || (testValue as ServerRenderResult).redirectLocation || (testValue as ServerRenderResult).routeError || - (testValue as ServerRenderResult).error); + (testValue as ServerRenderResult).error + ); } -export function isPromise(testValue: CreateReactOutputResult | Promise | string | null): - testValue is Promise { - return !!((testValue as Promise | null)?.then); +export function isPromise( + testValue: CreateReactOutputResult | Promise | string | null, +): testValue is Promise { + return !!(testValue as Promise | null)?.then; } diff --git a/node_package/src/pageLifecycle.ts b/node_package/src/pageLifecycle.ts index 630b0ab0c..aec8caa27 100644 --- a/node_package/src/pageLifecycle.ts +++ b/node_package/src/pageLifecycle.ts @@ -35,23 +35,19 @@ function setupTurbolinksEventListeners(): void { } if (turboInstalled()) { - debugTurbolinks( - 'USING TURBO: document added event listeners ' + - 'turbo:before-render and turbo:render.'); + debugTurbolinks('USING TURBO: document added event listeners turbo:before-render and turbo:render.'); document.addEventListener('turbo:before-render', runPageUnloadedCallbacks); document.addEventListener('turbo:render', runPageLoadedCallbacks); runPageLoadedCallbacks(); } else if (turbolinksVersion5()) { debugTurbolinks( - 'USING TURBOLINKS 5: document added event listeners ' + - 'turbolinks:before-render and turbolinks:render.'); + 'USING TURBOLINKS 5: document added event listeners turbolinks:before-render and turbolinks:render.', + ); document.addEventListener('turbolinks:before-render', runPageUnloadedCallbacks); document.addEventListener('turbolinks:render', runPageLoadedCallbacks); runPageLoadedCallbacks(); } else { - debugTurbolinks( - 'USING TURBOLINKS 2: document added event listeners page:before-unload and ' + - 'page:change.'); + debugTurbolinks('USING TURBOLINKS 2: document added event listeners page:before-unload and page:change.'); document.addEventListener('page:before-unload', runPageUnloadedCallbacks); document.addEventListener('page:change', runPageLoadedCallbacks); } diff --git a/node_package/src/reactApis.ts b/node_package/src/reactApis.ts deleted file mode 100644 index 63810197d..000000000 --- a/node_package/src/reactApis.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as ReactDOM from 'react-dom'; - -const reactMajorVersion = Number(ReactDOM.version?.split('.')[0]) || 16; - -// TODO: once we require React 18, we can remove this and inline everything guarded by it. -// Not the default export because others may be added for future React versions. -// eslint-disable-next-line import/prefer-default-export -export const supportsRootApi = reactMajorVersion >= 18; diff --git a/node_package/src/reactHydrateOrRender.ts b/node_package/src/reactHydrateOrRender.ts index dbac22a96..9ffbc2da3 100644 --- a/node_package/src/reactHydrateOrRender.ts +++ b/node_package/src/reactHydrateOrRender.ts @@ -1,43 +1,16 @@ -import type { ReactElement } from 'react'; -import * as ReactDOM from 'react-dom'; -import type { RenderReturnType } from './types'; -import { supportsRootApi } from './reactApis'; - -type HydrateOrRenderType = (domNode: Element, reactElement: ReactElement) => RenderReturnType; - -// TODO: once React dependency is updated to >= 18, we can remove this and just -// import ReactDOM from 'react-dom/client'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -let reactDomClient: any; -if (supportsRootApi) { - // This will never throw an exception, but it's the way to tell Webpack the dependency is optional - // https://github.com/webpack/webpack/issues/339#issuecomment-47739112 - // Unfortunately, it only converts the error to a warning. - try { - // eslint-disable-next-line global-require,import/no-unresolved - reactDomClient = require('react-dom/client'); - } catch (e) { - // We should never get here, but if we do, we'll just use the default ReactDOM - // and live with the warning. - reactDomClient = ReactDOM; - } -} - -const reactHydrate: HydrateOrRenderType = supportsRootApi ? - reactDomClient.hydrateRoot : - (domNode, reactElement) => ReactDOM.hydrate(reactElement, domNode); - -function reactRender(domNode: Element, reactElement: ReactElement): RenderReturnType { - if (supportsRootApi) { - const root = reactDomClient.createRoot(domNode); - root.render(reactElement); - return root; - } - - // eslint-disable-next-line react/no-render-return-value - return ReactDOM.render(reactElement, domNode); -} - -export default function reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType { - return hydrate ? reactHydrate(domNode, reactElement) : reactRender(domNode, reactElement); -} +import type { ReactElement } from 'react'; +import { createRoot, hydrateRoot, Root } from 'react-dom/client'; + +export default function reactHydrateOrRender( + domNode: Element, + reactElement: ReactElement, + hydrate: boolean, +): Root { + if (hydrate) { + return hydrateRoot(domNode, reactElement); + } + + const root = createRoot(domNode); + root.render(reactElement); + return root; +} diff --git a/node_package/src/registerServerComponent/client.ts b/node_package/src/registerServerComponent/client.ts index aa5e71f38..cefcbfab5 100644 --- a/node_package/src/registerServerComponent/client.ts +++ b/node_package/src/registerServerComponent/client.ts @@ -1,10 +1,6 @@ import ReactOnRails from '../ReactOnRails.client'; import RSCClientRoot from '../RSCClientRoot'; -import { - RegisterServerComponentOptions, - RailsContext, - ReactComponentOrRenderFunction, -} from '../types'; +import { RegisterServerComponentOptions, RailsContext, ReactComponentOrRenderFunction } from '../types'; /** * Registers React Server Components (RSC) with React on Rails. @@ -34,18 +30,27 @@ import { * registerServerComponent({ * rscPayloadGenerationUrlPath: '/rsc_payload' * }, 'ServerComponent1', 'ServerComponent2'); - * + * * // When ServerComponent1 renders, it will fetch from: /rsc_payload/ServerComponent1 * ``` */ const registerServerComponent = (options: RegisterServerComponentOptions, ...componentNames: string[]) => { const componentsWrappedInRSCClientRoot: Record = {}; for (const name of componentNames) { - componentsWrappedInRSCClientRoot[name] = (componentProps?: unknown, _railsContext?: RailsContext, domNodeId?: string) => RSCClientRoot({ - componentName: name, - rscPayloadGenerationUrlPath: options.rscPayloadGenerationUrlPath, - componentProps, - }, _railsContext, domNodeId); + componentsWrappedInRSCClientRoot[name] = ( + componentProps?: unknown, + _railsContext?: RailsContext, + domNodeId?: string, + ) => + RSCClientRoot( + { + componentName: name, + rscPayloadGenerationUrlPath: options.rscPayloadGenerationUrlPath, + componentProps, + }, + _railsContext, + domNodeId, + ); } ReactOnRails.register(componentsWrappedInRSCClientRoot); }; diff --git a/node_package/src/registerServerComponent/server.ts b/node_package/src/registerServerComponent/server.ts index c08e87e46..451d93765 100644 --- a/node_package/src/registerServerComponent/server.ts +++ b/node_package/src/registerServerComponent/server.ts @@ -17,7 +17,7 @@ import { ReactComponent } from '../types'; * - Instead, a RSCServerRoot component is added to the ComponentRegistry * - This RSCServerRoot component will use the pre-generated RSC payloads from the RSC bundle to * build the rendering tree of the server component instead of rendering it again - * + * * This functionality is added now without real implementation to avoid breaking changes in the future. * * @param components - Object mapping component names to their implementations diff --git a/node_package/src/serverRenderReactComponent.ts b/node_package/src/serverRenderReactComponent.ts index 9e3e4b0db..f7a5e3bcc 100644 --- a/node_package/src/serverRenderReactComponent.ts +++ b/node_package/src/serverRenderReactComponent.ts @@ -7,7 +7,14 @@ import { isPromise, isServerRenderHash } from './isServerRenderResult'; import buildConsoleReplay from './buildConsoleReplay'; import handleError from './handleError'; import { createResultObject, convertToError, validateComponent } from './serverRenderUtils'; -import type { CreateReactOutputResult, RenderParams, RenderResult, RenderState, RenderOptions, ServerRenderResult } from './types'; +import type { + CreateReactOutputResult, + RenderParams, + RenderResult, + RenderState, + RenderOptions, + ServerRenderResult, +} from './types'; function processServerRenderHash(result: ServerRenderResult, options: RenderOptions): RenderState { const { redirectLocation, routeError } = result; @@ -21,7 +28,9 @@ function processServerRenderHash(result: ServerRenderResult, options: RenderOpti if (redirectLocation) { if (options.trace) { const redirectPath = redirectLocation.pathname + redirectLocation.search; - console.log(`ROUTER REDIRECT: ${options.componentName} to dom node with id: ${options.domNodeId}, redirect to ${redirectPath}`); + console.log( + `ROUTER REDIRECT: ${options.componentName} to dom node with id: ${options.domNodeId}, redirect to ${redirectPath}`, + ); } // For redirects on server rendering, we can't stop Rails from returning the same result. // Possibly, someday, we could have the Rails server redirect. @@ -33,9 +42,14 @@ function processServerRenderHash(result: ServerRenderResult, options: RenderOpti return { result: htmlResult, hasErrors }; } -function processPromise(result: Promise, renderingReturnsPromises: boolean): Promise | string { +function processPromise( + result: Promise, + renderingReturnsPromises: boolean, +): Promise | string { if (!renderingReturnsPromises) { - console.error('Your render function returned a Promise, which is only supported by a node renderer, not ExecJS.'); + console.error( + 'Your render function returned a Promise, which is only supported by a node renderer, not ExecJS.', + ); // If the app is using server rendering with ExecJS, then the promise will not be awaited. // And when a promise is passed to JSON.stringify, it will be converted to '{}'. return '{}'; @@ -64,7 +78,7 @@ function processRenderingResult(result: CreateReactOutputResult, options: Render return { result: processReactElement(result), hasErrors: false }; } -function handleRenderingError(e: unknown, options: { componentName: string, throwJsErrors: boolean }) { +function handleRenderingError(e: unknown, options: { componentName: string; throwJsErrors: boolean }) { if (options.throwJsErrors) { throw e; } @@ -79,7 +93,7 @@ function handleRenderingError(e: unknown, options: { componentName: string, thro async function createPromiseResult( renderState: RenderState & { result: Promise }, componentName: string, - throwJsErrors: boolean + throwJsErrors: boolean, ): Promise { // Capture console history before awaiting the promise // Node renderer will reset the global console.history after executing the synchronous part of the request. @@ -100,7 +114,7 @@ async function createPromiseResult( function createFinalResult( renderState: RenderState, componentName: string, - throwJsErrors: boolean + throwJsErrors: boolean, ): null | string | Promise { const { result } = renderState; if (isPromise(result)) { @@ -112,7 +126,15 @@ function createFinalResult( } function serverRenderReactComponentInternal(options: RenderParams): null | string | Promise { - const { name: componentName, domNodeId, trace, props, railsContext, renderingReturnsPromises, throwJsErrors } = options; + const { + name: componentName, + domNodeId, + trace, + props, + railsContext, + renderingReturnsPromises, + throwJsErrors, + } = options; let renderState: RenderState = { result: null, @@ -136,7 +158,12 @@ function serverRenderReactComponentInternal(options: RenderParams): null | strin // 1. Converts React elements to HTML strings // 2. Returns rendered HTML from serverRenderHash // 3. Handles promises for async rendering - renderState = processRenderingResult(reactRenderingResult, { componentName, domNodeId, trace, renderingReturnsPromises }); + renderState = processRenderingResult(reactRenderingResult, { + componentName, + domNodeId, + trace, + renderingReturnsPromises, + }); } catch (e: unknown) { renderState = handleRenderingError(e, { componentName, throwJsErrors }); } diff --git a/node_package/src/serverRenderUtils.ts b/node_package/src/serverRenderUtils.ts index 1c83e2d2d..c77926cec 100644 --- a/node_package/src/serverRenderUtils.ts +++ b/node_package/src/serverRenderUtils.ts @@ -1,12 +1,18 @@ - import type { RegisteredComponent, RenderResult, RenderState, StreamRenderState } from './types'; -export function createResultObject(html: string | null, consoleReplayScript: string, renderState: RenderState | StreamRenderState): RenderResult { +export function createResultObject( + html: string | null, + consoleReplayScript: string, + renderState: RenderState | StreamRenderState, +): RenderResult { return { html, consoleReplayScript, hasErrors: renderState.hasErrors, - renderingError: renderState.error && { message: renderState.error.message, stack: renderState.error.stack }, + renderingError: renderState.error && { + message: renderState.error.message, + stack: renderState.error.stack, + }, isShellReady: 'isShellReady' in renderState ? renderState.isShellReady : undefined, }; } @@ -17,6 +23,8 @@ export function convertToError(e: unknown): Error { export function validateComponent(componentObj: RegisteredComponent, componentName: string) { if (componentObj.isRenderer) { - throw new Error(`Detected a renderer while server rendering component '${componentName}'. See https://github.com/shakacode/react_on_rails#renderer-functions`); + throw new Error( + `Detected a renderer while server rendering component '${componentName}'. See https://github.com/shakacode/react_on_rails#renderer-functions`, + ); } } diff --git a/node_package/src/streamServerRenderedReactComponent.ts b/node_package/src/streamServerRenderedReactComponent.ts index 19d4ad3fc..6a3bd46b2 100644 --- a/node_package/src/streamServerRenderedReactComponent.ts +++ b/node_package/src/streamServerRenderedReactComponent.ts @@ -20,18 +20,18 @@ const stringToStream = (str: string): Readable => { type BufferdEvent = { event: 'data' | 'error' | 'end'; data: unknown; -} +}; /** * Creates a new Readable stream that safely buffers all events from the input stream until reading begins. - * + * * This function solves two important problems: * 1. Error handling: If an error occurs on the source stream before error listeners are attached, * it would normally crash the process. This wrapper buffers error events until reading begins, * ensuring errors are properly handled once listeners are ready. * 2. Event ordering: All events (data, error, end) are buffered and replayed in the exact order * they were received, maintaining the correct sequence even if events occur before reading starts. - * + * * @param stream - The source Readable stream to buffer * @returns {Object} An object containing: * - stream: A new Readable stream that will buffer and replay all events @@ -41,7 +41,7 @@ const bufferStream = (stream: Readable) => { const bufferedEvents: BufferdEvent[] = []; let startedReading = false; - const listeners = (['data', 'error', 'end'] as const).map(event => { + const listeners = (['data', 'error', 'end'] as const).map((event) => { const listener = (data: unknown) => { if (!startedReading) { bufferedEvents.push({ event, data }); @@ -72,10 +72,10 @@ const bufferStream = (stream: Readable) => { bufferedEvents.forEach(handleEvent); // Attach new listeners for future events - (['data', 'error', 'end'] as const).forEach(event => { + (['data', 'error', 'end'] as const).forEach((event) => { stream.on(event, (data: unknown) => handleEvent({ event, data })); }); - } + }, }); return { @@ -87,7 +87,7 @@ const bufferStream = (stream: Readable) => { bufferedEvents.push({ event: 'error', data: error }); } }, - } + }; }; export const transformRenderStreamChunksToResultObject = (renderState: StreamRenderState) => { @@ -104,7 +104,7 @@ export const transformRenderStreamChunksToResultObject = (renderState: StreamRen this.push(`${jsonChunk}\n`); callback(); - } + }, }); let pipedStream: ReactDOMServer.PipeableStream | null = null; @@ -123,25 +123,20 @@ export const transformRenderStreamChunksToResultObject = (renderState: StreamRen const endStream = () => { transformStream.end(); pipedStream?.abort(); - } + }; return { readableStream, pipeToTransform, writeChunk, emitError, endStream }; -} +}; const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: RenderParams) => { const { name: componentName, throwJsErrors, domNodeId } = options; const renderState: StreamRenderState = { result: null, hasErrors: false, - isShellReady: false + isShellReady: false, }; - const { - readableStream, - pipeToTransform, - writeChunk, - emitError, - endStream - } = transformRenderStreamChunksToResultObject(renderState); + const { readableStream, pipeToTransform, writeChunk, emitError, endStream } = + transformRenderStreamChunksToResultObject(renderState); const renderingStream = ReactDOMServer.renderToPipeableStream(reactRenderingResult, { onShellError(e) { @@ -176,16 +171,13 @@ const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: }); return readableStream; -} +}; -type StreamRenderer = ( - reactElement: ReactElement, - options: P, -) => T; +type StreamRenderer = (reactElement: ReactElement, options: P) => T; export const streamServerRenderedComponent = ( options: P, - renderStrategy: StreamRenderer + renderStrategy: StreamRenderer, ): T => { const { name: componentName, domNodeId, trace, props, railsContext, throwJsErrors } = options; @@ -213,11 +205,14 @@ export const streamServerRenderedComponent = ( const error = convertToError(e); const htmlResult = handleError({ e: error, name: componentName, serverSide: true }); - const jsonResult = JSON.stringify(createResultObject(htmlResult, buildConsoleReplay(), { hasErrors: true, error, result: null })); + const jsonResult = JSON.stringify( + createResultObject(htmlResult, buildConsoleReplay(), { hasErrors: true, error, result: null }), + ); return stringToStream(jsonResult) as T; } }; -const streamServerRenderedReactComponent = (options: RenderParams): Readable => streamServerRenderedComponent(options, streamRenderReactComponent); +const streamServerRenderedReactComponent = (options: RenderParams): Readable => + streamServerRenderedComponent(options, streamRenderReactComponent); export default streamServerRenderedReactComponent; diff --git a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts index d5219d029..4ea9c1d9d 100644 --- a/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts +++ b/node_package/src/transformRSCStreamAndReplayConsoleLogs.ts @@ -13,7 +13,7 @@ export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableS lastIncompleteChunk = chunks.pop() ?? ''; const jsonChunks = chunks - .filter(line => line.trim() !== '') + .filter((line) => line.trim() !== '') .map((line) => { try { return JSON.parse(line); @@ -27,7 +27,10 @@ export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableS const { html, consoleReplayScript = '' } = jsonChunk; controller.enqueue(encoder.encode(html)); - const replayConsoleCode = consoleReplayScript.trim().replace(/^/, '').replace(/<\/script>$/, ''); + const replayConsoleCode = consoleReplayScript + .trim() + .replace(/^/, '') + .replace(/<\/script>$/, ''); if (replayConsoleCode?.trim() !== '') { const scriptElement = document.createElement('script'); scriptElement.textContent = replayConsoleCode; @@ -39,6 +42,6 @@ export default function transformRSCStreamAndReplayConsoleLogs(stream: ReadableS ({ value, done } = await reader.read()); } controller.close(); - } + }, }); } diff --git a/node_package/src/turbolinksUtils.ts b/node_package/src/turbolinksUtils.ts index a94c769b5..7c2da7676 100644 --- a/node_package/src/turbolinksUtils.ts +++ b/node_package/src/turbolinksUtils.ts @@ -20,7 +20,7 @@ export function debugTurbolinks(...msg: string[]): void { } export function turbolinksInstalled(): boolean { - return (typeof Turbolinks !== 'undefined'); + return typeof Turbolinks !== 'undefined'; } export function turboInstalled() { @@ -32,7 +32,7 @@ export function turboInstalled() { } export function turbolinksVersion5(): boolean { - return (typeof Turbolinks.controller !== 'undefined'); + return typeof Turbolinks.controller !== 'undefined'; } export function turbolinksSupported(): boolean { diff --git a/node_package/src/types/index.ts b/node_package/src/types/index.ts index bdd30661e..fd98a00cd 100644 --- a/node_package/src/types/index.ts +++ b/node_package/src/types/index.ts @@ -1,7 +1,8 @@ // eslint-disable-next-line spaced-comment /// -import type { ReactElement, ReactNode, Component, ComponentType } from 'react'; +import type { ReactElement, ComponentType } from 'react'; +import type { Root } from 'react-dom/client'; import type { Readable } from 'stream'; // Don't import redux just for the type definitions @@ -33,13 +34,16 @@ export interface RailsContext { httpAcceptLanguage: string; } -type AuthenticityHeaders = {[id: string]: string} & {'X-CSRF-Token': string | null; 'X-Requested-With': string}; +type AuthenticityHeaders = { [id: string]: string } & { + 'X-CSRF-Token': string | null; + 'X-Requested-With': string; +}; -type StoreGenerator = (props: Record, railsContext: RailsContext) => Store +type StoreGenerator = (props: Record, railsContext: RailsContext) => Store; interface ServerRenderResult { renderedHtml?: string | { componentHtml: string; [key: string]: string }; - redirectLocation?: {pathname: string; search: string}; + redirectLocation?: { pathname: string; search: string }; routeError?: Error; error?: Error; } @@ -51,23 +55,23 @@ type RenderFunctionResult = ReactComponent | ServerRenderResult | Promise { ... }; - * + * * // Option 2: Using renderFunction property * const anotherRenderFunction = (props) => { ... }; * anotherRenderFunction.renderFunction = true; @@ -81,7 +85,7 @@ interface RenderFunction { type ReactComponentOrRenderFunction = ReactComponent | RenderFunction; -export type { // eslint-disable-line import/prefer-default-export +export type { ReactComponentOrRenderFunction, ReactComponent, AuthenticityHeaders, @@ -91,7 +95,7 @@ export type { // eslint-disable-line import/prefer-default-export StoreGenerator, CreateReactOutputResult, ServerRenderResult, -} +}; export interface RegisteredComponent { name: string; @@ -155,14 +159,6 @@ export interface RenderResult { isShellReady?: boolean; } -// from react-dom 18 -export interface Root { - render(children: ReactNode): void; - unmount(): void; -} - -export type RenderReturnType = void | Element | Component | Root; - export interface ReactOnRails { register(components: { [id: string]: ReactComponentOrRenderFunction }): void; /** @deprecated Use registerStoreGenerators instead */ @@ -171,8 +167,8 @@ export interface ReactOnRails { getStore(name: string, throwIfMissing?: boolean): Store | undefined; getOrWaitForStore(name: string): Promise; getOrWaitForStoreGenerator(name: string): Promise; - setOptions(newOptions: {traceTurbolinks: boolean}): void; - reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType; + setOptions(newOptions: { traceTurbolinks: boolean }): void; + reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): Root; reactOnRailsPageLoaded(): Promise; reactOnRailsComponentLoaded(domId: string): void; reactOnRailsStoreLoaded(storeName: string): void; @@ -182,9 +178,7 @@ export interface ReactOnRails { getStoreGenerator(name: string): StoreGenerator; setStore(name: string, store: Store): void; clearHydratedStores(): void; - render( - name: string, props: Record, domNodeId: string, hydrate: boolean - ): RenderReturnType; + render(name: string, props: Record, domNodeId: string, hydrate: boolean): Root; getComponent(name: string): RegisteredComponent; getOrWaitForComponent(name: string): Promise; serverRenderReactComponent(options: RenderParams): null | string | Promise; diff --git a/node_package/src/utils.ts b/node_package/src/utils.ts index ed2ccda18..87118a8c0 100644 --- a/node_package/src/utils.ts +++ b/node_package/src/utils.ts @@ -6,7 +6,7 @@ const customFetch = (...args: Parameters) => { const res = fetch(...args); return res; -} +}; // eslint-disable-next-line import/prefer-default-export export { customFetch as fetch }; diff --git a/package-scripts.yml b/package-scripts.yml index f5c159a16..25d5b4d67 100644 --- a/package-scripts.yml +++ b/package-scripts.yml @@ -1,7 +1,7 @@ scripts: lint: description: Run all linters (eslint, tsc) - script: concurrently --prefix "[{name}]" --names "ESLINT" -c "blue,yellow,magenta,orange" "nps eslint" + script: nps eslint eslint: default: @@ -33,32 +33,10 @@ scripts: format: default: description: Format files using prettier. - script: concurrently --prefix "[{name}]" --names "ts,js,json" -c "yellow,magenta,green" "nps format.js" "nps format.json" + script: prettier --write . listDifferent: description: Check that all files were formatted using prettier. - script: | - concurrently \ - --prefix "[{name}]" \ - --names "ts,js,json" \ - -c "yellow,magenta" \ - "nps format.js.listDifferent" \ - "nps format.json.listDifferent" - js: - default: - description: Run prettier-eslint on JS. - #script: prettier "packages/**/*.@(js|jsx)" "spec/dummy/client/app/**/*.@(js|jsx)" "webpack.config.babel.js" "webpack/**/*.js" --write - script: prettier "**/*.@(js|jsx)" --write - listDifferent: - description: Check if any JS files would change by running prettier-eslint. - # script: prettier "**/*.@(js|jsx)" "webpack.config.babel.js" "webpack/**/*.js" --list-different - script: prettier "**/*.@(js|jsx)" --list-different - json: - default: - description: Run prettier on JSON files. - script: rm -rf packages/vm-renderer/tests/tmp && prettier "**/*.json" --write - listDifferent: - description: Check if any JSON files would change by running prettier-eslint. - script: prettier "**/*.json" --list-different + script: prettier --check . renderer: description: Starts the node renderer. diff --git a/package.json b/package.json index ab26b7c1e..fba3ad9a2 100644 --- a/package.json +++ b/package.json @@ -31,15 +31,14 @@ "@types/turbolinks": "^5.2.2", "@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/parser": "^6.18.1", - "concurrently": "^8.2.2", "create-react-class": "^15.7.0", "eslint": "^7.32.0", - "eslint-config-prettier": "^7.0.0", + "eslint-config-prettier": "^10.1.1", "eslint-config-shakacode": "^16.0.1", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.33.2", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -47,7 +46,7 @@ "jsdom": "^22.1.0", "knip": "^5.43.1", "nps": "^5.9.3", - "prettier": "^2.8.8", + "prettier": "^3.5.2", "prop-types": "^15.8.1", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -57,8 +56,8 @@ "typescript": "^5.6.2" }, "peerDependencies": { - "react": ">= 16", - "react-dom": ">= 16", + "react": ">= 18", + "react-dom": ">= 18", "react-on-rails-rsc": "19.0.0" }, "files": [ diff --git a/rakelib/examples_config.yml b/rakelib/examples_config.yml index 043d6fb01..731c9d972 100644 --- a/rakelib/examples_config.yml +++ b/rakelib/examples_config.yml @@ -1,14 +1,9 @@ example_type_data: - - - name: basic - generator_options: "" - - - name: basic-server-rendering + - name: basic + generator_options: '' + - name: basic-server-rendering generator_options: --example-server-rendering - - - name: redux + - name: redux generator_options: --redux - - - name: redux-server-rendering + - name: redux-server-rendering generator_options: --redux --example-server-rendering - diff --git a/spec/dummy/.prettierignore b/spec/dummy/.prettierignore deleted file mode 100644 index 0cea569e5..000000000 --- a/spec/dummy/.prettierignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -public/webpack/ -*.res.js -lib/bs/** - -# File Generated by ROR FS-based Registry -/client/app/packs/server-bundle-generated.js -/client/app/packs/generated diff --git a/spec/dummy/.prettierrc b/spec/dummy/.prettierrc deleted file mode 100644 index 4ad6f5e58..000000000 --- a/spec/dummy/.prettierrc +++ /dev/null @@ -1,20 +0,0 @@ -printWidth: 110 -tabWidth: 2 -useTabs: false -semi: true -singleQuote: true -trailingComma: all -bracketSpacing: true -jsxBracketSameLine: false -parser: flow - -overrides: -- files: "*.@(css|scss)" - options: - parser: css - singleQuote: false - printWidth: 120 -- files: "*.@(json)" - options: - parser: json - printWidth: 100 diff --git a/spec/dummy/README.md b/spec/dummy/README.md index c4b578c64..fe59172fb 100644 --- a/spec/dummy/README.md +++ b/spec/dummy/README.md @@ -1,7 +1,7 @@ Using NPM for react_on_rails -* Use 'yalc link' to hook up the spec/dummy/client/node_modules to the top level -* Be sure to install yarn dependencies in spec/dummy/client +- Use 'yalc link' to hook up the spec/dummy/client/node_modules to the top level +- Be sure to install yarn dependencies in spec/dummy/client ## Initial setup @@ -22,14 +22,13 @@ yalc link react-on-rails ```sh cd react_on_rails -yarn run dummy:install +yarn run dummy:install cd spec/dummy yarn build:rescript ``` # Starting the Sample App - ## Hot Reloading of Rails Assets ```sh @@ -37,11 +36,13 @@ foreman start -f Procfile.dev ``` ## Static Loading of Rails Assets + ```sh foreman start -f Procfile.dev-static ``` ## Creating Assets for Tests + ```sh foreman start -f Procfile.spec ``` diff --git a/spec/dummy/app/assets/stylesheets/application_non_webpack.scss b/spec/dummy/app/assets/stylesheets/application_non_webpack.scss index 841137568..b7a9a7754 100644 --- a/spec/dummy/app/assets/stylesheets/application_non_webpack.scss +++ b/spec/dummy/app/assets/stylesheets/application_non_webpack.scss @@ -12,7 +12,7 @@ * */ -input[type='text'] { +input[type="text"] { min-width: 400px; } diff --git a/spec/dummy/client/.jscsrc b/spec/dummy/client/.jscsrc deleted file mode 100644 index 0be7eea16..000000000 --- a/spec/dummy/client/.jscsrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "preset": "airbnb", - "fileExtensions": [".js", ".jsx"], - "excludeFiles": ["build/**", "node_modules/**"] -} diff --git a/spec/dummy/client/README.md b/spec/dummy/client/README.md index 191858993..4b9b68b62 100644 --- a/spec/dummy/client/README.md +++ b/spec/dummy/client/README.md @@ -1,34 +1,30 @@ Please see parent directory README.md. -ESLint -========================== +# ESLint + The `.eslintrc` file is based on the AirBnb [eslintrc](https://github.com/airbnb/javascript/blob/master/linters/.eslintrc). It also includes many eslint defaults that the AirBnb eslint does not include. -Running linter: -=========================== +# Running linter: Running the linter: yarn run lint or to autofix - + yarn run lint -- --fix - - -Updating Node Dependencies -=========================== + +# Updating Node Dependencies ``` yarn global add npm-check-updates ``` - - + ``` # Make sure you are in the `client` directory, then run: -cd client +cd client npm-check-updates -u -a yarn ``` @@ -40,10 +36,10 @@ yarn upgrade ``` Then confirm that the hot reload server and the rails server both work fine. You -may have to delete `node_modules`. +may have to delete `node_modules`. + +# Adding Node Modules -Adding Node Modules -===================================== Suppose you want to add a dependency to "module_name".... Before you do so, consider: @@ -54,6 +50,6 @@ Before you do so, consider: ```bash cd client yarn add module_name@version -# or +# or # yarn add --dev module_name@version ``` diff --git a/spec/dummy/client/app/components/CssModulesImagesFontsExample.module.scss b/spec/dummy/client/app/components/CssModulesImagesFontsExample.module.scss index e17ccb5f0..811a8bdd3 100644 --- a/spec/dummy/client/app/components/CssModulesImagesFontsExample.module.scss +++ b/spec/dummy/client/app/components/CssModulesImagesFontsExample.module.scss @@ -2,11 +2,11 @@ @use "sass:math"; // TODO: Figure out how to get this in a global spot -$font-family-sans-serif: 'OpenSans-Light'; // apply custom font -$fonts-url-path: '../assets/fonts'; +$font-family-sans-serif: "OpenSans-Light"; // apply custom font +$fonts-url-path: "../assets/fonts"; @font-face { - font-family: 'OpenSans-Light'; - src: url('#{$fonts-url-path}/OpenSans-Light.ttf') format('truetype'); + font-family: "OpenSans-Light"; + src: url("#{$fonts-url-path}/OpenSans-Light.ttf") format("truetype"); } .heading { @@ -39,7 +39,7 @@ $hookipa-beach-height: 373px; } // This is an absolute path to the image, defined in app-variables.scss -$images-url-path: '../assets/images'; +$images-url-path: "../assets/images"; $rails-on-maui-width: 140px; $rails-on-maui-height: 40px; diff --git a/spec/dummy/client/app/components/ImageExample/ImageExample.module.scss b/spec/dummy/client/app/components/ImageExample/ImageExample.module.scss index 230c4cc3e..e6c782388 100644 --- a/spec/dummy/client/app/components/ImageExample/ImageExample.module.scss +++ b/spec/dummy/client/app/components/ImageExample/ImageExample.module.scss @@ -1,5 +1,5 @@ .red { - color: red + color: red; } .background { diff --git a/spec/dummy/client/app/startup/RouterApp.server.jsx b/spec/dummy/client/app/startup/RouterApp.server.jsx index 92ad5c2be..2995b42c2 100644 --- a/spec/dummy/client/app/startup/RouterApp.server.jsx +++ b/spec/dummy/client/app/startup/RouterApp.server.jsx @@ -3,9 +3,8 @@ import { StaticRouter } from 'react-router-dom'; import routes from '../routes/routes'; -export default (props, railsContext) => () => - ( - - {routes} - - ); +export default (props, railsContext) => () => ( + + {routes} + +); diff --git a/spec/dummy/config/locales/en.yml b/spec/dummy/config/locales/en.yml index 8ca56fc74..105801565 100644 --- a/spec/dummy/config/locales/en.yml +++ b/spec/dummy/config/locales/en.yml @@ -30,4 +30,4 @@ # available at https://guides.rubyonrails.org/i18n.html. en: - hello: "Hello world" + hello: 'Hello world' diff --git a/spec/dummy/config/shakapacker.yml b/spec/dummy/config/shakapacker.yml index a26756123..c58b28459 100644 --- a/spec/dummy/config/shakapacker.yml +++ b/spec/dummy/config/shakapacker.yml @@ -45,7 +45,7 @@ development: # Should we use gzip compression? compress: true # Note that apps that do not check the host are vulnerable to DNS rebinding attacks - allowed_hosts: "all" + allowed_hosts: 'all' pretty: true headers: 'Access-Control-Allow-Origin': '*' @@ -53,7 +53,6 @@ development: watch: ignored: '**/node_modules/**' - test: <<: *default compile: false diff --git a/spec/dummy/config/storage.yml b/spec/dummy/config/storage.yml index 4942ab669..667f5e7aa 100644 --- a/spec/dummy/config/storage.yml +++ b/spec/dummy/config/storage.yml @@ -5,7 +5,6 @@ test: local: service: Disk root: <%= Rails.root.join("storage") %> - # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) # amazon: # service: S3 diff --git a/spec/react_on_rails/fixtures/i18n/locales/de.yml b/spec/react_on_rails/fixtures/i18n/locales/de.yml index 3f02f787e..fa7b34aa9 100644 --- a/spec/react_on_rails/fixtures/i18n/locales/de.yml +++ b/spec/react_on_rails/fixtures/i18n/locales/de.yml @@ -1,2 +1,2 @@ de: - hello: "Hallo welt" + hello: 'Hallo welt' diff --git a/spec/react_on_rails/fixtures/i18n/locales/en.yml b/spec/react_on_rails/fixtures/i18n/locales/en.yml index 33add5904..0103833c0 100644 --- a/spec/react_on_rails/fixtures/i18n/locales/en.yml +++ b/spec/react_on_rails/fixtures/i18n/locales/en.yml @@ -1,6 +1,6 @@ en: - hello: "Hello world" - argument: "I am %{age} years old." + hello: 'Hello world' + argument: 'I am %{age} years old.' day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] blank: number: 2 diff --git a/yarn.lock b/yarn.lock index b8af5e475..cda8b0565 100644 --- a/yarn.lock +++ b/yarn.lock @@ -981,21 +981,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.12.5": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" - integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.21.0": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" - integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.23.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650" integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw== @@ -2347,21 +2333,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concurrently@^8.2.2: - version "8.2.2" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-8.2.2.tgz#353141985c198cfa5e4a3ef90082c336b5851784" - integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg== - dependencies: - chalk "^4.1.2" - date-fns "^2.30.0" - lodash "^4.17.21" - rxjs "^7.8.1" - shell-quote "^1.8.1" - spawn-command "0.0.2" - supports-color "^8.1.1" - tree-kill "^1.2.2" - yargs "^17.7.2" - convert-source-map@^1.6.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" @@ -2473,13 +2444,6 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" -date-fns@^2.30.0: - version "2.30.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" - debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -2826,10 +2790,10 @@ eslint-config-airbnb@16.1.0: dependencies: eslint-config-airbnb-base "^12.1.0" -eslint-config-prettier@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz#f4a4bd2832e810e8cc7c1411ec85b3e85c0c53f9" - integrity sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg== +eslint-config-prettier@^10.1.1: + version "10.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz#cf0ff6e5c4e7e15f129f1f1ce2a5ecba92dec132" + integrity sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw== eslint-config-shakacode@^16.0.1: version "16.0.1" @@ -2905,10 +2869,10 @@ eslint-plugin-jsx-a11y@^6.8.0: object.entries "^1.1.7" object.fromentries "^2.0.7" -eslint-plugin-prettier@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" - integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g== +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== dependencies: prettier-linter-helpers "^1.0.0" @@ -4978,10 +4942,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.8.8: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +prettier@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" + integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== pretty-format@^27.0.2: version "27.5.1" @@ -5159,11 +5123,6 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - regenerator-runtime@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" @@ -5297,13 +5256,6 @@ run-parallel@^1.2.0: dependencies: queue-microtask "^1.2.2" -rxjs@^7.8.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - safe-array-concat@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" @@ -5386,11 +5338,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" - integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -5457,7 +5404,7 @@ spawn-command-with-kill@^1.0.0: ps-tree "^1.2.0" spawn-command "^0.0.2-1" -spawn-command@0.0.2, spawn-command@^0.0.2-1: +spawn-command@^0.0.2-1: version "0.0.2" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== @@ -5625,7 +5572,7 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0, supports-color@^8.1.1: +supports-color@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -5745,11 +5692,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - ts-api-utils@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" @@ -5780,11 +5722,6 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.1.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" - integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -6206,7 +6143,7 @@ yargs@^16.0.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.3.1, yargs@^17.7.2: +yargs@^17.3.1: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==