webpacker 5.1.1 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +8 -8
  3. data/.github/workflows/jest.yml +38 -0
  4. data/.github/workflows/js-lint.yml +39 -0
  5. data/.github/workflows/rubocop.yml +39 -0
  6. data/.github/workflows/ruby.yml +70 -0
  7. data/.node-version +1 -1
  8. data/CHANGELOG.md +9 -3
  9. data/Gemfile.lock +76 -76
  10. data/README.md +11 -19
  11. data/docs/css.md +58 -3
  12. data/docs/integrations.md +1 -1
  13. data/docs/target.md +22 -0
  14. data/docs/testing.md +1 -1
  15. data/docs/troubleshooting.md +3 -1
  16. data/docs/typescript.md +44 -0
  17. data/docs/webpack-dev-server.md +1 -1
  18. data/lib/install/config/babel.config.js +1 -3
  19. data/lib/install/config/webpacker.yml +1 -1
  20. data/lib/tasks/webpacker/check_node.rake +1 -1
  21. data/lib/tasks/webpacker/check_yarn.rake +1 -1
  22. data/lib/webpacker/compiler.rb +7 -2
  23. data/lib/webpacker/configuration.rb +12 -4
  24. data/lib/webpacker/dev_server_runner.rb +2 -2
  25. data/lib/webpacker/helper.rb +25 -6
  26. data/lib/webpacker/railtie.rb +2 -2
  27. data/lib/webpacker/runner.rb +1 -0
  28. data/lib/webpacker/version.rb +1 -1
  29. data/lib/webpacker/webpack_runner.rb +2 -2
  30. data/package.json +29 -29
  31. data/package/__tests__/config.js +12 -1
  32. data/package/__tests__/development.js +14 -1
  33. data/package/config.js +4 -1
  34. data/package/configPath.js +3 -0
  35. data/package/env.js +1 -2
  36. data/package/environments/__tests__/base.js +26 -9
  37. data/package/environments/base.js +5 -6
  38. data/package/environments/development.js +39 -33
  39. data/package/environments/production.js +1 -3
  40. data/package/rules/babel.js +11 -4
  41. data/package/rules/file.js +1 -0
  42. data/package/rules/node_modules.js +1 -3
  43. data/package/rules/sass.js +1 -1
  44. data/package/utils/helpers.js +1 -1
  45. data/test/compiler_test.rb +8 -3
  46. data/test/configuration_test.rb +8 -7
  47. data/test/dev_server_runner_test.rb +1 -1
  48. data/test/helper_test.rb +3 -0
  49. data/test/test_app/config/webpacker.yml +7 -1
  50. data/test/test_app/public/packs/manifest.json +1 -0
  51. data/test/webpack_runner_test.rb +1 -1
  52. data/yarn.lock +2422 -2932
  53. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 120db32ca99baa277a5452ed1c68cc0fa842e60aac86214f7329e2979b0d81ea
4
- data.tar.gz: 47c5eef5ff70a8436523b776e595e59269cac206e7b88e2f37bf4716a944f9d6
3
+ metadata.gz: 6ca1d1bd58719c72f514bf91eda1ce7e3098ec6965de55d880bcbea2caeca5dc
4
+ data.tar.gz: 478cb03b14fc32f64b46c4c2ff6adbcae5778fb4ff70a88aaeee22c8c9debec6
5
5
  SHA512:
6
- metadata.gz: 784c1ad63862030eac771b060a82fbec37a73513f0cc82f2b08e13e0687a32ab673dcfb550bc73f22be420035a66cf39fb8425458e6660500758108875f2fe8f
7
- data.tar.gz: b52f0a2ac48b0fcfb07900d800976bcc2b8f99b46ad8109f7283088584d456b0f1f0129a7d5b1028060128cb7dcc04927ac2b98da965963e0a29adf626868b7a
6
+ metadata.gz: 7c91f086879b0d7fcbbf15fccdc2eaf65f9fd1419c8946eab708ac83db68ee2a29f12ed8db79380f4333ee193fd025a9496b81ce460b7f2388e74420b2484c48
7
+ data.tar.gz: 5266e49c79810132fe885992d0fca258d793f47a004550b66dce0cf7aebd75456fdaf2e42a153e74a07a2890e5d3f298f7f5789af345bd8f3db2ae22dee54900
@@ -1,14 +1,14 @@
1
1
  module.exports = {
2
- 'extends': 'airbnb',
3
- 'rules': {
2
+ extends: 'airbnb',
3
+ rules: {
4
4
  'comma-dangle': ['error', 'never'],
5
5
  'import/no-unresolved': 'off',
6
6
  'import/no-extraneous-dependencies': 'off',
7
7
  'import/extensions': 'off',
8
- semi: ['error', 'never'],
8
+ semi: ['error', 'never']
9
9
  },
10
- 'env': {
11
- 'browser': true,
12
- 'node': true,
13
- },
14
- };
10
+ env: {
11
+ browser: true,
12
+ node: true
13
+ }
14
+ }
@@ -0,0 +1,38 @@
1
+ name: Jest specs
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: Jest specs
8
+ strategy:
9
+ matrix:
10
+ os: [ubuntu-latest]
11
+ node: [10.x, 12.x, 14.x]
12
+
13
+ runs-on: ${{ matrix.os }}
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Get yarn cache directory path
18
+ id: yarn-cache-dir-path
19
+ run: echo "::set-output name=dir::$(yarn cache dir)"
20
+
21
+ - uses: actions/cache@v2
22
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
23
+ with:
24
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
25
+ key: ${{ runner.os }}-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
26
+ restore-keys: |
27
+ ${{ runner.os }}-${{ matrix.node }}-yarn-
28
+
29
+ - name: Use Node.js ${{ matrix.node }}
30
+ uses: actions/setup-node@v1
31
+ with:
32
+ node-version: ${{ matrix.node }}
33
+
34
+ - name: Install dependencies
35
+ run: yarn --frozen-lockfile
36
+
37
+ - name: Jest Specs
38
+ run: yarn test
@@ -0,0 +1,39 @@
1
+ name: JS lint
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: JS Lint
8
+
9
+ strategy:
10
+ matrix:
11
+ os: [ubuntu-latest]
12
+ node: [12.x]
13
+
14
+ runs-on: ${{ matrix.os }}
15
+
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - name: Get yarn cache directory path
19
+ id: yarn-cache-dir-path
20
+ run: echo "::set-output name=dir::$(yarn cache dir)"
21
+
22
+ - uses: actions/cache@v2
23
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
24
+ with:
25
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
26
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
27
+ restore-keys: |
28
+ ${{ runner.os }}-yarn-
29
+
30
+ - name: Use Node.js ${{ matrix.node }}
31
+ uses: actions/setup-node@v1
32
+ with:
33
+ node-version: ${{ matrix.node }}
34
+
35
+ - name: Install dependencies
36
+ run: yarn --frozen-lockfile
37
+
38
+ - name: Lint
39
+ run: yarn lint
@@ -0,0 +1,39 @@
1
+ name: Rubocop
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: Rubocop
8
+ runs-on: ${{ matrix.os }}
9
+ env:
10
+ BUNDLE_JOBS: 4
11
+ BUNDLE_RETRY: 3
12
+ strategy:
13
+ matrix:
14
+ os: [ubuntu-latest]
15
+ ruby: [
16
+ 2.7
17
+ ]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - uses: actions/cache@v2
22
+ with:
23
+ path: /home/runner/bundle
24
+ key: bundle-use-ruby-gems-${{ hashFiles('**/Gemfile.lock') }}
25
+ restore-keys: |
26
+ bundle-use-ruby-gems-
27
+
28
+ - uses: ruby/setup-ruby@v1
29
+ with:
30
+ ruby-version: ${{ matrix.ruby }}
31
+
32
+ - name: Bundle install
33
+ run: |
34
+ gem install bundler -v 2.1.4
35
+ bundle config path /home/runner/bundle
36
+ bundle install
37
+
38
+ - name: Ruby linter
39
+ run: bundle exec rubocop
@@ -0,0 +1,70 @@
1
+ name: Ruby specs
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: Ruby specs
8
+ runs-on: ${{ matrix.os }}
9
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' || matrix.experimental }}
10
+ env:
11
+ BUNDLE_JOBS: 4
12
+ BUNDLE_RETRY: 3
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest]
17
+ ruby: [
18
+ 2.4,
19
+ 2.5,
20
+ 2.6,
21
+ 2.7
22
+ ]
23
+ gemfile: [
24
+ "gemfiles/Gemfile-rails.5.2.x",
25
+ "gemfiles/Gemfile-rails.6.0.x"
26
+ ]
27
+ exclude:
28
+ - ruby: "2.4"
29
+ gemfile: gemfiles/Gemfile-rails.6.0.x
30
+ experimental: [false]
31
+ include:
32
+ - ruby: head
33
+ os: ubuntu-latest
34
+ gemfile: gemfiles/Gemfile-rails.6.0.x
35
+ experimental: true
36
+ - ruby: head
37
+ os: ubuntu-latest
38
+ gemfile: gemfiles/Gemfile-rails-edge
39
+ experimental: true
40
+ - ruby: 2.6
41
+ os: ubuntu-latest
42
+ gemfile: gemfiles/Gemfile-rails-edge
43
+ experimental: true
44
+ - ruby: 2.7
45
+ os: ubuntu-latest
46
+ gemfile: gemfiles/Gemfile-rails-edge
47
+ experimental: true
48
+
49
+ steps:
50
+ - uses: actions/checkout@v2
51
+ - uses: actions/cache@v2
52
+ with:
53
+ path: /home/runner/bundle
54
+ key: bundle-use-ruby-${{ matrix.ruby }}-${{ matrix.gemfile }}-gems-${{ hashFiles(matrix.gemfile) }}-${{ hashFiles('**/*.gemspec') }}
55
+ restore-keys: |
56
+ bundle-use-ruby-${{ matrix.ruby }}-${{ matrix.gemfile }}-gems-
57
+
58
+ - uses: ruby/setup-ruby@v1
59
+ with:
60
+ ruby-version: ${{ matrix.ruby }}
61
+
62
+ - name: Bundle install
63
+ run: |
64
+ gem install bundler -v 2.1.4
65
+ bundle config path /home/runner/bundle
66
+ bundle config --global gemfile ${{ matrix.gemfile }}
67
+ bundle install --jobs 4 --retry 3
68
+
69
+ - name: Ruby specs
70
+ run: bundle exec rake test
@@ -1 +1 @@
1
- 10.13.0
1
+ 10.17.0
@@ -2,6 +2,10 @@
2
2
 
3
3
  **Please note that Webpacker 4.1.0 has an installer bug. Please use 4.2.0 or above**
4
4
 
5
+ ## [[5.2.0]](https://github.com/rails/webpacker/compare/v5.1.1...5.2.0) - 2020-08-16
6
+
7
+ - Bump dependencies and fixes. See [diff](https://github.com/rails/webpacker/compare/v5.1.1...5-x-stable) for changes.
8
+
5
9
  ## [[5.1.1]](https://github.com/rails/webpacker/compare/v5.1.0...v5.1.1) - 2020-04-20
6
10
 
7
11
  - Update [TypeScript documentation](https://github.com/rails/webpacker/blob/master/docs/typescript.md) and installer to use babel-loader for typescript.[(#2541](https://github.com/rails/webpacker/pull/2541)
@@ -9,7 +13,7 @@
9
13
  ## [[5.1.0]](https://github.com/rails/webpacker/compare/v5.0.1...v5.1.0) - 2020-04-19
10
14
 
11
15
  - Remove yarn integrity check [#2518](https://github.com/rails/webpacker/pull/2518)
12
- - Switch from ts-loader to babel-loader [#2449](https://github.com/rails/webpacker/pull/2449)
16
+ - Switch from ts-loader to babel-loader [#2449](https://github.com/rails/webpacker/pull/2449)
13
17
  Please see the [TypeScript documentation](https://github.com/rails/webpacker/blob/master/docs/typescript.md) to upgrade existing projects to use typescript with 5.1
14
18
  - Resolve multi-word snakecase WEBPACKER_DEV_SERVER env values [#2528](https://github.com/rails/webpacker/pull/2528)
15
19
 
@@ -325,7 +329,7 @@ const { environment } = require('@rails/webpacker')
325
329
  // Enable with default config
326
330
  environment.splitChunks()
327
331
  // Configure via a callback
328
- environment.splitChunks(config =>
332
+ environment.splitChunks((config) =>
329
333
  Object.assign({}, config, { optimization: { splitChunks: false } })
330
334
  )
331
335
  ```
@@ -649,7 +653,9 @@ environment.resolvedModules.append('vendor', 'vendor')
649
653
  ```js
650
654
  // Enable css modules with sass loader
651
655
  const sassLoader = environment.loaders.get('sass')
652
- const cssLoader = sassLoader.use.find(loader => loader.loader === 'css-loader')
656
+ const cssLoader = sassLoader.use.find(
657
+ (loader) => loader.loader === 'css-loader'
658
+ )
653
659
 
654
660
  cssLoader.options = Object.assign({}, cssLoader.options, {
655
661
  modules: true,
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- webpacker (5.1.1)
4
+ webpacker (5.2.0)
5
5
  activesupport (>= 5.2)
6
6
  rack-proxy (>= 0.6.1)
7
7
  railties (>= 5.2)
@@ -10,123 +10,123 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- actioncable (6.0.1)
14
- actionpack (= 6.0.1)
13
+ actioncable (6.0.3.2)
14
+ actionpack (= 6.0.3.2)
15
15
  nio4r (~> 2.0)
16
16
  websocket-driver (>= 0.6.1)
17
- actionmailbox (6.0.1)
18
- actionpack (= 6.0.1)
19
- activejob (= 6.0.1)
20
- activerecord (= 6.0.1)
21
- activestorage (= 6.0.1)
22
- activesupport (= 6.0.1)
17
+ actionmailbox (6.0.3.2)
18
+ actionpack (= 6.0.3.2)
19
+ activejob (= 6.0.3.2)
20
+ activerecord (= 6.0.3.2)
21
+ activestorage (= 6.0.3.2)
22
+ activesupport (= 6.0.3.2)
23
23
  mail (>= 2.7.1)
24
- actionmailer (6.0.1)
25
- actionpack (= 6.0.1)
26
- actionview (= 6.0.1)
27
- activejob (= 6.0.1)
24
+ actionmailer (6.0.3.2)
25
+ actionpack (= 6.0.3.2)
26
+ actionview (= 6.0.3.2)
27
+ activejob (= 6.0.3.2)
28
28
  mail (~> 2.5, >= 2.5.4)
29
29
  rails-dom-testing (~> 2.0)
30
- actionpack (6.0.1)
31
- actionview (= 6.0.1)
32
- activesupport (= 6.0.1)
33
- rack (~> 2.0)
30
+ actionpack (6.0.3.2)
31
+ actionview (= 6.0.3.2)
32
+ activesupport (= 6.0.3.2)
33
+ rack (~> 2.0, >= 2.0.8)
34
34
  rack-test (>= 0.6.3)
35
35
  rails-dom-testing (~> 2.0)
36
36
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
- actiontext (6.0.1)
38
- actionpack (= 6.0.1)
39
- activerecord (= 6.0.1)
40
- activestorage (= 6.0.1)
41
- activesupport (= 6.0.1)
37
+ actiontext (6.0.3.2)
38
+ actionpack (= 6.0.3.2)
39
+ activerecord (= 6.0.3.2)
40
+ activestorage (= 6.0.3.2)
41
+ activesupport (= 6.0.3.2)
42
42
  nokogiri (>= 1.8.5)
43
- actionview (6.0.1)
44
- activesupport (= 6.0.1)
43
+ actionview (6.0.3.2)
44
+ activesupport (= 6.0.3.2)
45
45
  builder (~> 3.1)
46
46
  erubi (~> 1.4)
47
47
  rails-dom-testing (~> 2.0)
48
48
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
- activejob (6.0.1)
50
- activesupport (= 6.0.1)
49
+ activejob (6.0.3.2)
50
+ activesupport (= 6.0.3.2)
51
51
  globalid (>= 0.3.6)
52
- activemodel (6.0.1)
53
- activesupport (= 6.0.1)
54
- activerecord (6.0.1)
55
- activemodel (= 6.0.1)
56
- activesupport (= 6.0.1)
57
- activestorage (6.0.1)
58
- actionpack (= 6.0.1)
59
- activejob (= 6.0.1)
60
- activerecord (= 6.0.1)
52
+ activemodel (6.0.3.2)
53
+ activesupport (= 6.0.3.2)
54
+ activerecord (6.0.3.2)
55
+ activemodel (= 6.0.3.2)
56
+ activesupport (= 6.0.3.2)
57
+ activestorage (6.0.3.2)
58
+ actionpack (= 6.0.3.2)
59
+ activejob (= 6.0.3.2)
60
+ activerecord (= 6.0.3.2)
61
61
  marcel (~> 0.3.1)
62
- activesupport (6.0.1)
62
+ activesupport (6.0.3.2)
63
63
  concurrent-ruby (~> 1.0, >= 1.0.2)
64
64
  i18n (>= 0.7, < 2)
65
65
  minitest (~> 5.1)
66
66
  tzinfo (~> 1.1)
67
- zeitwerk (~> 2.2)
68
- ast (2.4.0)
69
- builder (3.2.3)
70
- byebug (11.0.1)
71
- concurrent-ruby (1.1.5)
72
- crass (1.0.5)
67
+ zeitwerk (~> 2.2, >= 2.2.2)
68
+ ast (2.4.1)
69
+ builder (3.2.4)
70
+ byebug (11.1.3)
71
+ concurrent-ruby (1.1.6)
72
+ crass (1.0.6)
73
73
  erubi (1.9.0)
74
74
  globalid (0.4.2)
75
75
  activesupport (>= 4.2.0)
76
- i18n (1.7.0)
76
+ i18n (1.8.5)
77
77
  concurrent-ruby (~> 1.0)
78
78
  jaro_winkler (1.5.4)
79
- loofah (2.3.1)
79
+ loofah (2.6.0)
80
80
  crass (~> 1.0.2)
81
81
  nokogiri (>= 1.5.9)
82
82
  mail (2.7.1)
83
83
  mini_mime (>= 0.1.1)
84
84
  marcel (0.3.3)
85
85
  mimemagic (~> 0.3.2)
86
- method_source (0.9.2)
87
- mimemagic (0.3.3)
86
+ method_source (1.0.0)
87
+ mimemagic (0.3.5)
88
88
  mini_mime (1.0.2)
89
89
  mini_portile2 (2.4.0)
90
- minitest (5.13.0)
90
+ minitest (5.14.1)
91
91
  nio4r (2.5.2)
92
- nokogiri (1.10.8)
92
+ nokogiri (1.10.10)
93
93
  mini_portile2 (~> 2.4.0)
94
- parallel (1.18.0)
95
- parser (2.6.5.0)
96
- ast (~> 2.4.0)
97
- rack (2.0.8)
94
+ parallel (1.19.2)
95
+ parser (2.7.1.4)
96
+ ast (~> 2.4.1)
97
+ rack (2.2.3)
98
98
  rack-proxy (0.6.5)
99
99
  rack
100
100
  rack-test (1.1.0)
101
101
  rack (>= 1.0, < 3)
102
- rails (6.0.1)
103
- actioncable (= 6.0.1)
104
- actionmailbox (= 6.0.1)
105
- actionmailer (= 6.0.1)
106
- actionpack (= 6.0.1)
107
- actiontext (= 6.0.1)
108
- actionview (= 6.0.1)
109
- activejob (= 6.0.1)
110
- activemodel (= 6.0.1)
111
- activerecord (= 6.0.1)
112
- activestorage (= 6.0.1)
113
- activesupport (= 6.0.1)
102
+ rails (6.0.3.2)
103
+ actioncable (= 6.0.3.2)
104
+ actionmailbox (= 6.0.3.2)
105
+ actionmailer (= 6.0.3.2)
106
+ actionpack (= 6.0.3.2)
107
+ actiontext (= 6.0.3.2)
108
+ actionview (= 6.0.3.2)
109
+ activejob (= 6.0.3.2)
110
+ activemodel (= 6.0.3.2)
111
+ activerecord (= 6.0.3.2)
112
+ activestorage (= 6.0.3.2)
113
+ activesupport (= 6.0.3.2)
114
114
  bundler (>= 1.3.0)
115
- railties (= 6.0.1)
115
+ railties (= 6.0.3.2)
116
116
  sprockets-rails (>= 2.0.0)
117
117
  rails-dom-testing (2.0.3)
118
118
  activesupport (>= 4.2.0)
119
119
  nokogiri (>= 1.6)
120
120
  rails-html-sanitizer (1.3.0)
121
121
  loofah (~> 2.3)
122
- railties (6.0.1)
123
- actionpack (= 6.0.1)
124
- activesupport (= 6.0.1)
122
+ railties (6.0.3.2)
123
+ actionpack (= 6.0.3.2)
124
+ activesupport (= 6.0.3.2)
125
125
  method_source
126
126
  rake (>= 0.8.7)
127
127
  thor (>= 0.20.3, < 2.0)
128
128
  rainbow (3.0.0)
129
- rake (13.0.0)
129
+ rake (13.0.1)
130
130
  rubocop (0.68.1)
131
131
  jaro_winkler (~> 1.5.1)
132
132
  parallel (~> 1.10)
@@ -138,22 +138,22 @@ GEM
138
138
  rubocop (>= 0.68.0)
139
139
  ruby-progressbar (1.10.1)
140
140
  semantic_range (2.3.0)
141
- sprockets (4.0.0)
141
+ sprockets (4.0.2)
142
142
  concurrent-ruby (~> 1.0)
143
143
  rack (> 1, < 3)
144
144
  sprockets-rails (3.2.1)
145
145
  actionpack (>= 4.0)
146
146
  activesupport (>= 4.0)
147
147
  sprockets (>= 3.0.0)
148
- thor (0.20.3)
148
+ thor (1.0.1)
149
149
  thread_safe (0.3.6)
150
- tzinfo (1.2.5)
150
+ tzinfo (1.2.7)
151
151
  thread_safe (~> 0.1)
152
152
  unicode-display_width (1.5.0)
153
- websocket-driver (0.7.1)
153
+ websocket-driver (0.7.3)
154
154
  websocket-extensions (>= 0.1.0)
155
- websocket-extensions (0.1.4)
156
- zeitwerk (2.2.1)
155
+ websocket-extensions (0.1.5)
156
+ zeitwerk (2.4.0)
157
157
 
158
158
  PLATFORMS
159
159
  ruby
@@ -171,4 +171,4 @@ DEPENDENCIES
171
171
  webpacker!
172
172
 
173
173
  BUNDLED WITH
174
- 1.17.3
174
+ 2.1.4