miller
发布于

前端自动化grunt

grunt是什么?

grunt是一个非常好的自动化工具,你只管codeing,它会自动帮你将代码合并(concat)、压缩(uglify)、语法检查(jshint)、自动编译less(contrib-less)和sass(contrib-sass)、压缩图片(contrib-imagemin)、读写拷贝移动文件等等,极大地简化了你的工作,它有很多插件

Grunt 常用插件

Js 文件校验:grunt-contrib-jshint
文件拷贝:grunt-contrib-copy
文件删除:grunt-contrib-clean
文件合并:grunt-contrib-concat
Sass 解析:grunt-contrib-sass
Css 压缩:grunt-contrib-cssmin
Html 压缩:grunt-contrib-htmlmin
Js 压缩混淆:grunt-contrib-uglify
静态资源文件模版:grunt-usemin
静态资源文件版本:grunt-rev
任务运行时间插件:time-grunt
加载 Grunt 插件:load-grunt-tasks
自动添加样式前缀:grunt-autoprefixer
...

npm install grunt-contrib-watch --save-dev 安装时候顺带添加到devDependencies 中
执行任务方式eg: grunt copy:dist

eg: https://github.com/WorldWideTelescope/wwt-web-client

// Copyright 2020 the .NET Foundation
// Licensed under the MIT License

module.exports = function (grunt) {
  'use strict';

  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

  RegExp.quote = function (string) {
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      client: {
        files: ['*.html','views/**/*', 'css/*', 'ext/*', 'controllers/**/*', 'factories/*', 'images/*'], //需要监听的文件
        options: {
          livereload: true
        },
        tasks: [
          'uglify:webclient',
          'less:compileCore',
          'cssmin:minifyCore',
          'template:indexhtml','copy:dist',]
      }
    },
    banner: '/**\n' +
      '* AAS WorldWide Telescope Web Client\n' +
      '* Copyright 2014-2020 .NET Foundation\n' +
      '* Licensed under the MIT License\n' +
      '* Git hash <%= gitinfo.local.branch.current.SHA %>\n' +
      '**/\n',

    // Triger the loading of the Git version info
    gitinfo: {},

    // Different build profiles. The "localtest" file is in .gitignore and is
    // intended to be used for temporary local testing. The other profiles are
    // in Git for reproducibility.
    profile: {
      dev: 'profile-dev.yml',
      localtest: 'profile-localtest.yml',
      prod: 'profile-prod.yml',
      testing: 'profile-testing.yml',
    },

    // Task configuration.

    concat: {
      options: {
        banner: '<%= banner %>',

        process: function(src, filepath) {
          if (filepath == 'app.js' || filepath == 'index.html') {
            return grunt.template.process(src);
          } else {
            return src;
          }
        },
      },

      webclient: {
        src: [
          'ext/intro.js',
          'ext/angular-intro.js',

          'app.js',

          'directives/ContextMenu.js',
          'directives/CopyToClipboard.js',
          'directives/Localize.js',
          'directives/Scroll.js',
          'directives/editslidevalues.js',
          'directives/movable.js',

          'factories/AppState.js',
          'factories/AutohidePanels.js',
          'factories/FinderScope.js',
          'factories/HashManager.js',
          'factories/Localization.js',
          'factories/MediaFile.js',
          'factories/SearchUtil.js',
          'factories/Skyball.js',
          'factories/ThumbList.js',
          'factories/UILibrary.js',
          'factories/Util.js',

          'dataproxy/Astrometry.js',
          'dataproxy/Community.js',
          'dataproxy/Places.js',
          'dataproxy/SearchData.js',
          'dataproxy/Tours.js',

          'controllers/ContextPanelController.js',
          'controllers/IntroController.js',
          'controllers/LayerManagerController.js',
          'controllers/LoginController.js',
          'controllers/MainController.js',
          'controllers/MobileNavController.js',
          'controllers/modals/ColorPickerController.js',
          'controllers/modals/DataVizController.js',
          'controllers/modals/EmbedController.js',
          'controllers/modals/GreatCircleController.js',
          'controllers/modals/ObservingLocationController.js',
          'controllers/modals/ObservingTimeController.js',
          'controllers/modals/OpenItemController.js',
          'controllers/modals/ShareController.js',
          'controllers/modals/SlideSelectionController.js',
          'controllers/modals/TourSlideText.js',
          'controllers/modals/VOTableViewerController.js',
          'controllers/modals/VoConeSearchController.js',
          'controllers/modals/refFrameController.js',
          'controllers/tabs/CommunityController.js',
          'controllers/tabs/CurrentTourController.js',
          'controllers/tabs/ExploreController.js',
          'controllers/tabs/SearchController.js',
          'controllers/tabs/SettingsController.js',
          'controllers/tabs/ToursController.js',
          'controllers/tabs/ViewController.js',

          'misc/move.js',
          'misc/util.js'
        ],
        dest: 'dist/wwtwebclient.js',
        nonull: true,
      }
    },

    uglify: {
      options: {
        mangle: false,
        preserveComments: 'some',
        banner: '<%= banner %>'
      },
      webclient: {
        src: '<%= concat.webclient.dest %>',
        dest: 'dist/wwtwebclient.min.js'
      },
    },

    less: {
      compileCore: {
        options: {
          strictMath: true,
          sourceMap: true,
          outputSourceFiles: true,
          sourceMapURL: 'webclient.css.map',
          sourceMapFilename: 'dist/css/webclient.css.map'
        },
        src: 'css/webclient.less',
        dest: 'dist/css/webclient.css'
      }
    },

    autoprefixer: {
      options: {
        browsers: [
          "Android 2.3",
          "Android >= 4",
          "Chrome >= 20",
          "Firefox >= 24",
          "Explorer >= 10",
          "iOS >= 6",
          "Opera >= 12",
          "Safari >= 6"
        ]
      },
      core: {
        options: {
          map: true
        },
        src: '<%= less.compileCore.dest %>'
      }
    },

    cssmin: {
      options: {
        compatibility: 'ie10',
        keepSpecialComments: '*',
        noAdvanced: true
      },
      minifyCore: {
        src: '<%= autoprefixer.core.src %>',
        dest: 'dist/css/webclient.min.css'
      }
    },

    template: {
      options: {
        data: function() {
          var d = require('lodash').clone(grunt.config.get('profile_data'));
          d.shortSHA = grunt.config.get('gitinfo.local.branch.current.shortSHA');
          return d;
        }
      },

      indexhtml: {
        files: {
          'dist/index.html': 'index.html'
        }
      }
    },

    copy: {
      dist: {
        files: [
          {
            expand: false,
            src: [
              'assets/*',
              'css/introjs.css',
              'css/angular-motion.css',
              'css/mcecontent.css',
              'css/skin.min.css',
              'default.aspx',
              'favicon.ico'
            ],
            dest: 'dist/'
          },
          {
            expand: true,
            src: [
              'fonts/*',
              'images/*',
              'views/**'
            ],
            dest: 'dist/',
            filter: 'isFile'
          }
        ]
      }
    }
  });

  require('load-grunt-tasks')(grunt, {scope: 'devDependencies'});
  // 加载插件
  grunt.loadNpmTasks('grunt-contrib-watch');

  // 自定义任务
  grunt.registerTask('live', ['watch','profile:dev']);
  
  grunt.registerMultiTask('profile', 'Specify the build profile', function() {
    var data = grunt.file.readYAML(this.data);
    grunt.config.set('profile_data', data);
  });

  var all_tasks = [
    'gitinfo',
    'concat:webclient',
    'uglify:webclient',
    'less:compileCore',
    'autoprefixer:core',
    'cssmin:minifyCore',
    'template:indexhtml',
    'copy:dist'
  ];

  for (let profname of ['dev', 'localtest', 'prod', 'testing']) {
    grunt.registerTask('dist-' + profname, ['profile:' + profname].concat(all_tasks));
  }
};


执行 grunt live 就会启动监听,然后有修改,就会执行对应tasks

  • grunt live --force 跳过中间warn 错误。停止问题
  • pnpm grunt live --force // 中间require 引包问题
  • 环境变量 还有点问题 profile 默认加载进来

package.json 参考

  "buildConfig": {
    "uglify": true
  },
  "description": "AAS WorldWide Telescope web client",
  "devDependencies": {
    "connect-livereload": "^0.6.1",
    "grunt": "^1.6.1",
    "grunt-autoprefixer": "^3.0",
    "grunt-banner": "^0.6",
    "grunt-contrib-concat": "^1.0",
    "grunt-contrib-copy": "^1.0",
    "grunt-contrib-cssmin": "^3.0",
    "grunt-contrib-less": "^2.0",
    "grunt-contrib-uglify": "^4.0",
    "grunt-contrib-watch": "^1.1.0",
    "grunt-gitinfo": "^0.1",
    "grunt-template": "^1.0",
    "load-grunt-tasks": "^5.0",
    "tslib": "^1.10.0"
  },
浏览 (1384)
点赞
收藏
2条评论
miller
miller
https://blog.csdn.net/IndexMan/article/details/111150775 配合 LiveReload 插件, 实现热更 。 vs 手动刷新没区别
点赞
评论
miller
miller
pnpm grunt live --force
点赞
评论