1
import { types as t } from '@babel/core'
2
// @ts-ignore
3
import { addNamed } from '@babel/helper-module-imports'
4
import { NodePath, Visitor } from '@babel/traverse'
5
import { Declaration, Expression, Identifier } from '@babel/types'
6

7
interface Options {
8
    componentNamePattern: string | RegExp
9
    modulePattern: string | RegExp
10
}
11

12
interface State {
13
    opts: Options
14

15
    file: {
16
        opts: {
17
            filename: string | null
18
        }
19
    }
20

21
    /** To avoid adding multiple imports in the same file. */
22
    hotIdentifier?: Identifier
23
}
24

25 1
export default ({ types }: { types: typeof t }) => {
26 1
    const getHotIdentifier = (path: NodePath, state: State): Identifier => {
27 1
        const hotIdentifier: Identifier = state.hotIdentifier || addNamed(path, 'hot', 'react-hot-loader/root')
28 1
        state.hotIdentifier = hotIdentifier
29 1
        return hotIdentifier
30
    }
31

32 1
    const getComponentExpr = (node: Declaration): { name: string; expr: Expression } | null => {
33 1
        if (t.isVariableDeclaration(node)) {
34 1
            if (node.declarations.length !== 1) {
35 0
                return null
36
            }
37 1
            const decl = node.declarations[0]
38 1
            if (!types.isIdentifier(decl.id)) {
39 0
                return null
40
            }
41 1
            if (!t.isArrowFunctionExpression(decl.init)) {
42 1
                return null
43
            }
44 1
            return { name: decl.id.name, expr: decl.init }
45
        }
46 1
        if (t.isClassDeclaration(node)) {
47 1
            if (!node.id) {
48 0
                return null
49
            }
50 1
            const expr: t.ClassExpression = t.clone<any>(node)
51 1
            expr.type = 'ClassExpression'
52 1
            return {
53
                name: node.id.name,
54
                expr,
55
            }
56
        }
57 1
        if (t.isFunctionDeclaration(node)) {
58 1
            if (!node.id) {
59 0
                return null
60
            }
61 1
            const expr: t.FunctionExpression = t.clone<any>(node)
62 1
            expr.type = 'FunctionExpression'
63 1
            return {
64
                name: node.id.name,
65
                expr,
66
            }
67
        }
68 0
        return null
69
    }
70

71 1
    const getWrappedDeclaration = (
72
        path: NodePath,
73
        node: Declaration,
74
        state: State,
75
        componentNamePattern: RegExp
76 1
    ): Declaration | null => {
77 1
        const component = getComponentExpr(node)
78 1
        if (!component) {
79 1
            return null
80
        }
81 1
        if (!componentNamePattern.test(component.name)) {
82 1
            return null
83
        }
84 1
        return t.variableDeclaration('const', [
85
            t.variableDeclarator(
86
                types.identifier(component.name),
87
                types.callExpression(getHotIdentifier(path, state), [component.expr])
88
            ),
89
        ])
90
    }
91

92 1
    const visitor: Visitor<State> = {
93 1
        Program: (path, state) => {
94 1
            let { modulePattern } = state.opts
95 1
            if (typeof modulePattern !== 'string' && !(modulePattern instanceof RegExp)) {
96 0
                throw new Error(
97
                    '.modulePattern must be a string or RegExp (to match the paths of modules that should be processed).'
98
                )
99
            }
100 1
            if (typeof modulePattern === 'string') {
101 1
                modulePattern = new RegExp(modulePattern)
102
            }
103

104 1
            if (state.file.opts.filename === null || !modulePattern.test(state.file.opts.filename)) {
105 0
                return
106
            }
107

108 1
            path.traverse(
109
                {
110 1
                    ExportNamedDeclaration: (path, state) => {
111 1
                        let { componentNamePattern } = state.opts
112 1
                        if (typeof componentNamePattern !== 'string' && !(componentNamePattern instanceof RegExp)) {
113 0
                            throw new Error(
114
                                ".componentNamePattern must be a string or RegExp (to match the exported names that should be wrapped with react-hot-loader/root's `hot` function)."
115
                            )
116
                        }
117 1
                        if (typeof componentNamePattern === 'string') {
118 1
                            componentNamePattern = new RegExp(componentNamePattern)
119
                        }
120

121 1
                        if (path.node.declaration) {
122 1
                            const d = getWrappedDeclaration(path, path.node.declaration, state, componentNamePattern)
123 1
                            if (d) {
124 1
                                path.replaceWith(t.exportNamedDeclaration(d, []))
125
                            }
126
                        }
127
                    },
128
                },
129
                state
130
            )
131
        },
132
    }
133

134 1
    return {
135
        name: '@sourcegraph/babel-plugin-transform-react-hot-loader-wrapper',
136
        visitor,
137
    }
138
}

Read our documentation on viewing source code .

Loading