Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
/* jshint node: true, browser: false */
16
/* eslint-env node */
17
 
18
/**
19
 * @copyright  2021 Andrew Nicols
20
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21
 */
22
 
23
/**
24
 * Function to generate the destination for the minification task
25
 * (e.g. build/file.min.js). This function will be passed to
26
 * the rename property of files array when building dynamically:
27
 * http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
28
 *
29
 * @param {String} destPath the current destination
30
 * @param {String} srcPath the  matched src path
31
 * @return {String} The rewritten destination path.
32
 */
33
const babelRename = function(destPath, srcPath) {
34
    destPath = srcPath.replace(`amd/src`, `amd/build`);
35
    destPath = destPath.replace(/\.js$/, '.min.js');
36
    return destPath;
37
};
38
 
39
module.exports = grunt => {
40
    // Load the Ignorefiles tasks.
41
    require('./ignorefiles')(grunt);
42
 
43
    // Load the Shifter tasks.
44
    require('./shifter')(grunt);
45
 
46
    // Load ESLint.
47
    require('./eslint')(grunt);
48
 
49
    // Load jsconfig.
50
    require('./jsconfig')(grunt);
51
 
52
    // Load JSDoc.
53
    require('./jsdoc')(grunt);
54
 
55
    const path = require('path');
56
 
57
    // Register JS tasks.
58
    grunt.registerTask('yui', ['eslint:yui', 'shifter']);
59
    grunt.registerTask('amd', ['ignorefiles', 'eslint:amd', 'rollup']);
60
    grunt.registerTask('js', ['amd', 'yui']);
61
 
62
    // Register NPM tasks.
63
    grunt.loadNpmTasks('grunt-contrib-watch');
64
    grunt.loadNpmTasks('grunt-rollup');
65
 
66
    const babelTransform = require('@babel/core').transform;
67
    const babel = (options = {}) => {
68
        return {
69
            name: 'babel',
70
 
71
            transform: (code, id) => {
72
                grunt.log.debug(`Transforming ${id}`);
73
                options.filename = id;
74
                const transformed = babelTransform(code, options);
75
 
76
                return {
77
                    code: transformed.code,
78
                    map: transformed.map
79
                };
80
            }
81
        };
82
    };
83
 
84
    // Note: We have to use a rate limit plugin here because rollup runs all tasks asynchronously and in parallel.
85
    // When we kick off a full run, if we kick off a rollup of every file this will fork-bomb the machine.
86
    // To work around this we use a concurrent Promise queue based on the number of available processors.
87
    const rateLimit = () => {
88
        const queue = [];
89
        let queueRunner;
90
 
91
        const startQueue = () => {
92
            if (queueRunner) {
93
                return;
94
            }
95
 
96
            queueRunner = setTimeout(() => {
97
                const limit = Math.max(1, require('os').cpus().length / 2);
98
                grunt.log.debug(`Starting rollup with queue size of ${limit}`);
99
                runQueue(limit);
100
            }, 100);
101
        };
102
 
103
        // The queue runner will run the next `size` items in the queue.
104
        const runQueue = (size = 1) => {
105
            queue.splice(0, size).forEach(resolve => {
106
                grunt.log.debug(`Item resolved. Kicking off next one.`);
107
                resolve();
108
            });
109
        };
110
 
111
        return {
112
            name: 'ratelimit',
113
 
114
            // The options hook is run in parallel.
115
            // We can return an unresolved Promise which is queued for later resolution.
116
            options: async(options) => {
117
                return new Promise(resolve => {
118
                    queue.push(resolve);
119
                    startQueue();
120
                    return options;
121
                });
122
            },
123
 
124
            // When an item in the queue completes, start the next item in the queue.
125
            generateBundle: (options, bundle) => {
126
                grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
127
                runQueue();
128
            },
129
        };
130
    };
131
 
132
    const terser = require('rollup-plugin-terser').terser;
133
    grunt.config.merge({
134
        rollup: {
135
            options: {
136
                format: 'esm',
137
                dir: 'output',
138
                sourcemap: true,
139
                treeshake: false,
140
                context: 'window',
141
                plugins: [
142
                    rateLimit(),
143
                    babel({
144
                        sourceMaps: true,
145
                        comments: false,
146
                        compact: false,
147
                        plugins: [
148
                            'transform-es2015-modules-amd-lazy',
149
                            'system-import-transformer',
150
                            // This plugin modifies the Babel transpiling for "export default"
151
                            // so that if it's used then only the exported value is returned
152
                            // by the generated AMD module.
153
                            //
154
                            // It also adds the Moodle plugin name to the AMD module definition
155
                            // so that it can be imported as expected in other modules.
156
                            path.resolve('.grunt/babel-plugin-add-module-to-define.js')
157
                        ],
158
                        presets: [
159
                            ['@babel/preset-env', {
160
                                modules: false,
161
                                useBuiltIns: false
162
                            }]
163
                        ]
164
                    }),
165
 
166
                    terser({
167
                        // Do not mangle variables.
168
                        // Makes debugging easier.
169
                        mangle: false,
170
                    }),
171
                ],
172
            },
173
            dist: {
174
                files: [{
175
                    expand: true,
176
                    src: grunt.moodleEnv.files ? grunt.moodleEnv.files : grunt.moodleEnv.amdSrc,
177
                    rename: babelRename
178
                }],
179
            },
180
        },
181
    });
182
 
183
    grunt.config.merge({
184
        watch: {
185
            amd: {
186
                files: grunt.moodleEnv.inComponent
187
                    ? ['amd/src/*.js', 'amd/src/**/*.js']
188
                    : ['**/amd/src/**/*.js'],
189
                tasks: ['amd']
190
            },
191
        },
192
    });
193
 
194
    // Add the 'js' task as a startup task.
195
    grunt.moodleEnv.startupTasks.push('js');
196
 
197
    // On watch, we dynamically modify config to build only affected files. This
198
    // method is slightly complicated to deal with multiple changed files at once (copied
199
    // from the grunt-contrib-watch readme).
200
    let changedFiles = Object.create(null);
201
    const onChange = grunt.util._.debounce(function() {
202
        const files = Object.keys(changedFiles);
203
        grunt.config('rollup.dist.files', [{expand: true, src: files, rename: babelRename}]);
204
        changedFiles = Object.create(null);
205
    }, 200);
206
 
207
    grunt.event.on('watch', function(action, filepath) {
208
        changedFiles[filepath] = action;
209
        onChange();
210
    });
211
 
212
    return {
213
        babelRename,
214
    };
215
};