Skip to content

Commit 1d4d57f

Browse files
committed
Replaced the string-based bundle config with webpack's config writer.
This fixes the issue where restarts of the python process cause the bundle to be rebuilt. Additionally, it also makes it easier to hook into the low-level parts of the component bundling process and makes changes as needed. Re markfinger#32
1 parent 695cf0e commit 1d4d57f

File tree

10 files changed

+293
-348
lines changed

10 files changed

+293
-348
lines changed

react/bundle.py

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import json
12
import os
23
import re
34
import tempfile
45
from optional_django import staticfiles
56
from webpack.compiler import webpack
7+
from webpack.config_file import ConfigFile, JS
68
from js_host.conf import settings as js_host_settings
79
from .exceptions import ComponentSourceFileNotFound
8-
from .templates import BUNDLE_CONFIG, BUNDLE_TRANSLATE_CONFIG, DEVTOOL_CONFIG
910
from .conf import settings
1011

1112

@@ -19,63 +20,125 @@ def bundle_component(path, translate=None, path_to_react=None, devtool=None):
1920
if not os.path.exists(path):
2021
raise ComponentSourceFileNotFound(path)
2122

22-
filename = get_component_config_filename(path, translate=translate, path_to_react=path_to_react, devtool=devtool)
23-
return webpack(filename)
23+
config = generate_config_for_component(path, translate=translate, path_to_react=path_to_react, devtool=devtool)
2424

25-
# TODO: replace this with a deterministic config file writer in webpack
26-
COMPONENT_CONFIG_FILES = {}
25+
config_file = generate_config_file(config)
2726

27+
var = generate_var_from_path(path)
2828

29-
def get_component_config_filename(path, translate=None, path_to_react=None, devtool=None):
30-
cache_key = (path, translate, path_to_react, devtool)
31-
if cache_key in COMPONENT_CONFIG_FILES:
32-
return COMPONENT_CONFIG_FILES[cache_key]
29+
path_to_config_file = get_path_to_config_file(config_file, prefix=var + '.')
3330

34-
config = get_webpack_config(path, translate=translate, path_to_react=path_to_react, devtool=devtool)
35-
filename = tempfile.mkstemp(suffix='.webpack.config.js')[1]
36-
with open(filename, 'w') as config_file:
37-
config_file.write(config)
31+
return webpack(path_to_config_file)
3832

39-
COMPONENT_CONFIG_FILES[cache_key] = filename
4033

41-
return filename
34+
def get_path_to_config_file(config_file, prefix=None):
35+
path = config_file.generate_path_to_file(prefix=prefix)
36+
return config_file.write(path, force=False)
4237

4338

44-
def get_webpack_config(path, translate=None, path_to_react=None, devtool=None):
45-
if devtool is None:
46-
devtool = settings.DEVTOOL
39+
def generate_config_file(config):
40+
return ConfigFile(
41+
JS('var path = require("path");\n'),
42+
JS('module.exports = '), config, JS(';'),
43+
)
44+
45+
46+
def generate_config_for_component(path, translate=None, path_to_react=None, devtool=None):
47+
"""
48+
Generates a webpack config object to bundle a component
49+
"""
50+
51+
var = generate_var_from_path(path)
4752

4853
node_modules = os.path.join(js_host_settings.SOURCE_ROOT, 'node_modules')
4954

5055
if path_to_react is None:
5156
path_to_react = settings.PATH_TO_REACT or os.path.join(node_modules, 'react')
5257

53-
var = get_var_from_path(path)
58+
config = {
59+
'context': js_path_join(os.path.dirname(path)),
60+
'entry': '.' + os.path.sep + os.path.basename(path),
61+
'output': {
62+
'path': '[bundle_dir]/react-components',
63+
'filename': var + '-[hash].js',
64+
'libraryTarget': 'umd',
65+
'library': var
66+
},
67+
'externals': [{
68+
'react': {
69+
'commonjs2': js_path_join(path_to_react),
70+
'root': 'React'
71+
},
72+
'react/addons': {
73+
'commonjs2': js_path_join(path_to_react),
74+
'root': 'React'
75+
}
76+
}]
77+
}
5478

55-
translate_config = ''
5679
if translate:
57-
# JSX + ES6/7 support
58-
translate_config += BUNDLE_TRANSLATE_CONFIG.format(
59-
ext=os.path.splitext(path)[-1],
60-
node_modules=node_modules
61-
)
62-
63-
devtool_config = ''
80+
translate_test = settings.TRANSLATE_TEST or '/.jsx$/'
81+
82+
config.update({
83+
'module': {
84+
'loaders': [{
85+
'test': JS(translate_test),
86+
'exclude': JS('/node_modules/'),
87+
'loader': 'babel-loader'
88+
}]
89+
},
90+
'resolveLoader': {
91+
'root': js_path_join(node_modules)
92+
}
93+
})
6494

6595
if devtool:
66-
devtool_config = DEVTOOL_CONFIG.format(devtool=devtool)
67-
68-
return BUNDLE_CONFIG.format(
69-
path_to_react=path_to_react,
70-
dir=os.path.dirname(path),
71-
file='./' + os.path.basename(path),
72-
var=var,
73-
translate_config=translate_config,
74-
devtool_config=devtool_config,
75-
)
96+
config['devtool'] = devtool
97+
98+
return config
99+
100+
101+
def split_path(path):
102+
"""
103+
Splits a path into the various parts and returns a list
104+
"""
105+
106+
parts = []
107+
108+
drive, path = os.path.splitdrive(path)
109+
110+
while True:
111+
newpath, tail = os.path.split(path)
112+
113+
if newpath == path:
114+
assert not tail
115+
if path:
116+
parts.append(path)
117+
break
118+
119+
parts.append(tail)
120+
path = newpath
121+
122+
if drive:
123+
parts.append(drive)
124+
125+
parts.reverse()
126+
127+
return parts
128+
129+
130+
def js_path_join(path):
131+
"""
132+
Splits a path so that it can be rejoined by the JS engine. Helps to avoid
133+
OS compatibility issues due to string encoding
134+
"""
135+
return JS('path.join.apply(path, ' + json.dumps(split_path(path)) + ')')
76136

77137

78-
def get_var_from_path(path):
138+
def generate_var_from_path(path):
139+
"""
140+
Infer a variable name from a path
141+
"""
79142
var = '{parent_dir}__{filename}'.format(
80143
parent_dir=os.path.basename(os.path.dirname(path)),
81144
filename=os.path.splitext(os.path.basename(path))[0]

react/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ class Conf(conf.Conf):
88
# The default import path used when rendering components
99
PATH_TO_REACT = None
1010

11+
# A JavaScript regex which is used to test if a file should have the babel
12+
# loader run over it
13+
TRANSLATE_TEST = None
14+
1115
JS_HOST_FUNCTION = 'react'
1216

1317
settings = Conf()

react/templates.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,3 @@
1-
BUNDLE_CONFIG = \
2-
"""
3-
module.exports = {{
4-
context: '{dir}',
5-
entry: '{file}',
6-
output: {{
7-
path: '[bundle_dir]/react-components',
8-
filename: '{var}-[hash].js',
9-
libraryTarget: 'umd',
10-
library: '{var}'
11-
}},
12-
externals: [{{
13-
react: {{
14-
commonjs2: '{path_to_react}',
15-
root: 'React'
16-
}},
17-
'react/addons': {{
18-
commonjs2: '{path_to_react}',
19-
root: 'React'
20-
}}
21-
}}]{devtool_config}{translate_config}
22-
}};
23-
"""
24-
25-
DEVTOOL_CONFIG = """,\n devtool: '{devtool}'"""
26-
27-
BUNDLE_TRANSLATE_CONFIG = \
28-
""",
29-
module: {{
30-
loaders: [{{
31-
test: /\{ext}$/,
32-
exclude: /node_modules/,
33-
loader: 'babel-loader'
34-
}}]
35-
}},
36-
resolveLoader: {{
37-
root: '{node_modules}'
38-
}}
39-
"""
40-
411
MOUNT_JS = \
422
"""
433
if (typeof React === 'undefined') throw new Error('Cannot find `React` global variable. Have you added a script element to this page which points to React?');

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dependencies
22
optional-django==0.3.0
3-
webpack==4.0.1
3+
webpack==4.1.1
44

55
# Dev dependencies
66
js-host

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
version=react.__version__,
77
packages=['react'],
88
install_requires=[
9-
'webpack==4.0.1',
9+
'webpack==4.1.1',
1010
'optional-django==0.3.0',
1111
],
1212
description='Server-side rendering, client-side mounting, JSX translation, and component bundling',

tests/settings.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,15 @@
3434
WEBPACK = {
3535
'BUNDLE_ROOT': STATIC_ROOT,
3636
'BUNDLE_URL': '/static/',
37-
}
37+
}
38+
39+
40+
class Components(object):
41+
HELLO_WORLD_JS = os.path.join(COMPONENT_ROOT, 'HelloWorld.js')
42+
HELLO_WORLD_JSX = os.path.join(COMPONENT_ROOT, 'HelloWorld.jsx')
43+
REACT_ADDONS = os.path.join(COMPONENT_ROOT, 'ReactAddonsComponent.jsx')
44+
DJANGO_REL_PATH = 'django_test_app/StaticFileFinderComponent.jsx'
45+
PERF_TEST = os.path.join(COMPONENT_ROOT, 'PerfTestComponent.jsx')
46+
HELLO_WORLD_JSX_WRAPPER = os.path.join(COMPONENT_ROOT, 'HelloWorldWrapper.jsx')
47+
ERROR_THROWING = os.path.join(COMPONENT_ROOT, 'ErrorThrowingComponent.jsx')
48+
SYNTAX_ERROR = os.path.join(COMPONENT_ROOT, 'SyntaxErrorComponent.jsx')

0 commit comments

Comments
 (0)