Class: Condenser::JstTransformer

Inherits:
NodeProcessor show all
Defined in:
lib/condenser/transformers/jst_transformer.rb

Instance Attribute Summary

Attributes inherited from NodeProcessor

#npm_path

Instance Method Summary collapse

Methods inherited from NodeProcessor

#binary, call, #exec_runtime, #exec_runtime_error, #exec_syntax_error, #name, #npm_install, #npm_module_path, setup

Constructor Details

#initialize(dir = nil) ⇒ JstTransformer

Returns a new instance of JstTransformer.



3
4
5
6
7
# File 'lib/condenser/transformers/jst_transformer.rb', line 3

def initialize(dir = nil)
  super(dir)
  
  npm_install('@babel/core')
end

Instance Method Details

#call(environment, input) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/condenser/transformers/jst_transformer.rb', line 9

def call(environment, input)
  opts = {
    filename:         input[:filename],
    moduleId:         input[:filename].sub(/(\..+)+/, ''),
    cwd:              '/assets/',
    filenameRelative: input[:filename],
    sourceFileName:   input[:filename],
    ast:              false,
    compact:          false,
    plugins:          []
  }

  result = exec_runtime(<<-JS)
    const babel = require("#{npm_module_path('@babel/core')}");
    const source = #{JSON.generate(input[:source])};
    const options = #{JSON.generate(opts).gsub(/"@?babel[\/-][^"]+"/) { |m| "require(#{m})"}};

    let scope = [['document', 'window']];
    
    options['plugins'].unshift(function({ types: t }) {
      return {
        visitor: {
          Identifier(path, state) {
            if ( path.parent.type === 'MemberExpression' && path.parent.object !== path.node) {
              return;
            }
            
            if ( path.parent.type === 'ImportSpecifier' ||
                 path.parent.type === 'ImportDefaultSpecifier' ||
                 path.parent.type === 'FunctionDeclaration' ||
                 path.parent.type === 'FunctionExpression' ||
                 path.parent.type === 'ArrowFunctionExpression' ||
                 path.parent.type === 'SpreadElement' ||
                 path.parent.type === 'CatchClause' ) {
              return;
            }
            
            if ( path.parent.type === 'ObjectProperty' && path.parent.key === path.node ) {
              return;
            }

            if ( !(path.node.name in global) &&
                 !scope.find((s) => s.find(v => v === path.node.name))
            ) {
              path.replaceWith(
                t.memberExpression(t.identifier("locals"), path.node)
              );
            }
          }
        }
      };
    });
    

    options['plugins'].unshift(function({ types: t }) {
        return {
          visitor: {
            "FunctionDeclaration|FunctionExpression|ArrowFunctionExpression": {
              enter(path, state) {
                if (path.node.id) { scope[scope.length-1].push(path.node.id.name); }
                scope.push(path.node.params.map((n) => n.type === 'RestElement' ? n.argument.name : n.name));
              }
            },
            CatchClause: {
              enter(path, state) {
                scope.push([]);
                if (path.node.param.name) { scope[scope.length-1].push(path.node.param.name); }
              }
            },
            Scopable: {
              enter(path, state) {
                if (path.node.type !== 'Program' &&
                    path.node.type !== 'CatchClause' &&
                    path.parent.type !== 'FunctionDeclaration' &&
                    path.parent.type !== 'FunctionExpression' &&
                    path.parent.type !== 'ArrowFunctionExpression' &&
                    path.parent.type !== 'ExportDefaultDeclaration') {
                  scope.push([]);
                }
              },
              exit(path, state) {
                if (path.node.type !== 'Program' &&
                    path.parent.type !== 'ExportDefaultDeclaration') {
                  scope.pop();
                }
              }
            },
            ImportDeclaration(path, state) {
              path.node.specifiers.forEach((s) => scope[scope.length-1].push(s.local.name));
            },
            ClassDeclaration(path, state) {
              if (path.node.id) {
                scope[scope.length-1].push(path.node.id.name)
              }
            },
            VariableDeclaration(path, state) {
              path.node.declarations.forEach((s) => scope[scope.length-1].push(s.id.name));
            }
          }
        };
    });


    try {
      const result = babel.transform(source, options);
      console.log(JSON.stringify(result));
    } catch(e) {
      console.log(JSON.stringify({'error': [e.name, e.message, e.stack]}));
      process.exit(0);
    }
  JS

  if result['error']
    if result['error'][0] == 'SyntaxError'
      raise exec_syntax_error(result['error'][1], "/assets/#{input[:filename]}")
    else
      raise exec_runtime_error(result['error'][0] + ': ' + result['error'][1])
    end
  else
    input[:source] = result['code']
  end
  
  environment.preprocessors['application/javascript']&.each do |processor|
    processor_klass = (processor.is_a?(Class) ? processor : processor.class)
    input[:processors] << processor_klass.name
    environment.load_processors(processor_klass)
    processor.call(environment, input)
  end
end