webpack4 从零学习常用配置(小结)


Posted in Javascript onMay 28, 2019

webpack 的核心价值就是前端源码的打包,即将前端源码中每一个文件(无论任何类型)都当做一个 pack ,然后分析依赖,将其最终打包出线上运行的代码。webpack 的四个核心部分

  • entry 规定入口文件,一个或者多个
  • output 规定输出文件的位置
  • loader 各个类型的转换工具
  • plugin 打包过程中各种自定义功能的插件

webpack 如今已经进入 v4.x 版本,v5.x 估计也会很快发布。不过看 v5 的变化相比于 v4 ,常用的配置没有变,这是一个好消息,说明基本稳定。

前端工程师需要了解的 webpack

前端工程化是近几年前端发展迅速的主要推手之一,webpack 无疑是前端工程化的核心工具。目前前端工程化工具还没有到一键生成,或者重度继承到某个 IDE 中(虽然有些 cli 工具可以直接创建),还是需要开发人员手动做一些配置。

因此,作为前端开发人员,熟练应用 webpack 的常用配置、常用优化方案是必备的技能 —— 这也正是本文的内容。另外,webpack 的实现原理算是一个加分项,不要求所有开发人员掌握,本文也没有涉及。

基础配置

初始化环境

npm init -y 初始化 npm 环境,然后安装 webpack npm i webpack webpack-cli -D

新建 src 目录并在其中新建 index.js ,随便写点 console.log('index js') 。然后根目录创建 webpack.config.js ,内容如下

const path = require('path')

module.exports = {
  // mode 可选 development 或 production ,默认为后者
  // production 会默认压缩代码并进行其他优化(如 tree shaking)
  mode: 'development',
  entry: path.join(__dirname, 'src', 'index'),
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  }
}

然后增加 package.json 的 scripts

"scripts": {
  "build": "webpack"
 },

然后运行 npm run build 即可打包文件到 dist 目录。

区分 dev 和 build

使用 webpack 需要两个最基本的功能:第一,开发的代码运行一下看看是否有效;第二,开发完毕了将代码打包出来。这两个操作的需求、配置都是完全不一样的。例如,运行代码时不需要压缩以便 debug ,而打包代码时就需要压缩以减少文件体积。因此,这里我们还是先把两者分开,方便接下来各个步骤的讲解。

首先,安装 npm i webpack-merge -D ,然后根目录新建 build 目录,其中新建如下三个文件。

// webpack.common.js 公共的配置
const path = require('path')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
module.exports = {
  entry: path.join(srcPath, 'index')
}
// webpack.dev.js 运行代码的配置(该文件暂时用不到,先创建了,下文会用到)
const path = require('path')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
module.exports = smart(webpackCommonConf, {
  mode: 'development'
})
// webpack.prod.js 打包代码的配置
const path = require('path')
const webpackCommonConf = require('./webpack.common.js')
const { smart } = require('webpack-merge')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
module.exports = smart(webpackCommonConf, {
  mode: 'production',
  output: {
    filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
    path: distPath,
    // publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
  }
})

修改 package.json 中的 scripts

"scripts": {
  "build": "webpack --config build/webpack.prod.js"
 },

重新运行 npm run build 即可看到打包出来的代码。最后,别忘了将根目录下的 webpack.config.js 删除。

这将引发一个新的问题:js 代码中将如何判断是什么环境呢?需要借助 webpack.DefinedPlugin 插件来定义全局变量。可以在 webpack.dev.js 和 webpack.prod.js 中做如下配置:

// 引入 webpack
const webpack = require('webpack')

// 增加 webpack 配置
  plugins: [
    new webpack.DefinePlugin({
      // 注意:此处 webpack.dev.js 中写 'development' ,webpack.prod.js 中写 'production'
      ENV: JSON.stringify('development')
    })

最后,修改 src/index.js 只需加入一行 console.log(ENV) ,然后重启 npm run dev 即可看到效果。

JS 模块化

webpack 默认支持 js 各种模块化,如常见的 commonJS 和 ES6 Module 。但是推荐使用 ES6 Module ,因为 production 模式下,ES6 Module 会默认触发 tree shaking ,而 commonJS 则没有这个福利。究其原因,ES6 Module 是静态引用,在编译时即可确定依赖关系,而 commonJS 是动态引用。

不过使用 ES6 Module 时,ES6 的解构赋值语法这里有一个坑,例如 index.js 中有一行 import {fn, name} from './a.js' ,此时 a.js 中有以下几种写法,大家要注意!

// 正确写法一
export function fn() {
  console.log('fn')
}
export const name = 'b'
// 正确写法二
function fn() {
  console.log('fn')
}
const name = 'b'
export {
  fn,
  name
}
// 错误写法
function fn() {
  console.log('fn')
}
export default {
  fn,
  name: 'b'
}

该现象的具体原因可参考https://3water.com/article/162079.htm 。下文马上要讲解启动本地服务,读者可以马上写一个 demo 自己验证一下这个现象。

启动本地服务

上文创建的 webpack.dev.js 一直没使用,下面就要用起来。

使用 html

启动本地服务,肯定需要一个 html 页面作为载体,新建一个 src/index.html 并初始化内容

<!DOCTYPE html>
<html>
<head><title>Document</title></head>
<body>
  <p>this is index html</p>
</body>
</html>

要使用这个 html 文件,还需要安装 npm i html-webpack-plugin -D ,然后配置 build/webpack.common.js ,因为无论 dev 还是 prod 都需要打包 html 文件。

plugins: [
    new HtmlWebpackPlugin({
      template: path.join(srcPath, 'index.html'),
      filename: 'index.html'
    })
  ]

重新运行 npm run build 会发现打包出来了 dist/index.html ,且内部已经自动插入了打包的 js 文件。

webpack-dev-server

有了 html 和 js 文件,就可以启动服务了。首先安装 npm i webpack-dev-server -D ,然后打开 build/webpack.dev.js配置。只有运行代码才需要本地 server ,打包代码时不需要。

devServer: {
  port: 3000,
  progress: true, // 显示打包的进度条
  contentBase: distPath, // 根目录
  open: true, // 自动打开浏览器
  compress: true // 启动 gzip 压缩
}

打开 package.json 修改 scripts ,增加 "dev": "webpack-dev-server --config build/webpack.dev.js", 。然后运行 npm run dev ,打开浏览器访问 localhost:3000 即可看到效果。

解决跨域

实际开发中,server 端提供的端口地址和前端可能不同,导致 ajax 收到跨域限制。使用 webpack-dev-server 可配置代理,解决跨域问题。如有需要,在 build/webpack.dev.js 中增加如下配置。

devServer: {
    // 设置代理
    proxy: {
      // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
      '/api': 'http://localhost:3000',

      // 将本地 /api2/xxx 代理到 localhost:3000/xxx
      '/api2': {
        target: 'http://localhost:3000',
        pathRewrite: {
          '/api2': ''
        }
      }
    }

处理 ES6

使用 babel

由于现在浏览器还不能保证完全支持 ES6 ,将 ES6 编译为 ES5 ,需要借助 babel 这个神器。安装 babel npm i babel-loader @babel/core @babel/preset-env -D ,然后修改 build/webpack.common.js 配置

module: {
    rules: [
      {
        test: /\.js$/,
        loader: ['babel-loader'],
        include: srcPath,
        exclude: /node_modules/
      },
    ]
  },

还要根目录下新建一个 .babelrc json 文件,内容下

{
  "presets": ["@babel/preset-env"],
  "plugins": []
}

在 src/index.js 中加入一行 ES6 代码,如箭头函数 const fn = () => { console.log('this is fn') } 。然后重新运行 npm run dev,可以看到浏览器中加载的 js 中,这个函数已经被编译为 function 形式。

使用高级特性

babel 可以解析 ES6 大部分语法特性,但是无法解析 class 、静态属性、块级作用域,还有很多大于 ES6 版本的语法特性,如装饰器。因此,想要把日常开发中的 ES6 代码全部转换为 ES5 ,还需要借助很多 babel 插件。

安装 npm i @babel/plugin-proposal-class-properties @babel/plugin-transform-block-scoping @babel/plugin-transform-classes -D ,然后配置 .babelrc

{
  "presets": ["@babel/preset-env"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-block-scoping",
    "@babel/plugin-transform-classes"
  ]
}

在 src/index.js 中新增一段 class 代码,然后重新运行 npm run build ,打包出来的代码会将 class 转换为 function 形式。

source map

source map 用于反解析压缩代码中错误的行列信息,dev 时代码没有压缩,用不到 source map ,因此要配置 build/webpack.prod.js

// webpack 中 source map 的可选项,是情况选择一种:

// devtool: 'source-map' // 1. 生成独立的 source map 文件
// devtool: 'eval-source-map' // 2. 同 1 ,但不会产生独立的文件,集成到打包出来的 js 文件中
// devtool: 'cheap-module-source-map' // 3. 生成单独的 source map 文件,但没有列信息(因此文件体积较小)
devtool: 'cheap-module-eval-source-map' // 4. 同 3 ,但不会产生独立的文件,集成到打包出来的 js 文件中

生产环境下推荐使用 1 或者 3 ,即生成独立的 map 文件。修改之后,重新运行 npm run build ,会看到打包出来了 map 文件。

处理样式

在 webpack 看来,不仅仅是 js ,其他的文件也是一个一个的模块,通过相应的 loader 进行解析并最终产出。

处理 css

安装必要插件 npm i style-loader css-loader -D ,然后配置 build/webpack.common.js

module: {
    rules: [
      { /* js loader */ },
      {
        test: /\.css$/,
        loader: ['style-loader', 'css-loader'] // loader 的执行顺序是:从后往前
      }
    ]
  },

新建一个 css 文件,然后引入到 src/index.js 中 import './css/index.css' ,重新运行 npm run dev 即可看到效果。

处理 less

less sass 都是常用 css 预处理语言,以 less 为例讲解。安装必要插件 npm i less less-loader -D ,然后配置 build/webpack.common.js

 

{
        test: /\.less$/,
        loader: ['style-loader', 'css-loader', 'less-loader'] // 增加 'less-loader' ,注意顺序
      }

新建一个 less 文件,然后引入到 src/index.js 中 import './css/index.less' ,重新运行 npm run dev 即可看到效果。

自动添加前缀

一些 css3 的语法,例如 transform: rotate(45deg); 为了浏览器兼容性需要加一些前缀,如 webkit- ,可以通过 webpack 来自动添加。安装 npm i postcss-loader autoprefixer -D ,然后配置

{
        test: /\.css$/,
        loader: ['style-loader', 'css-loader', 'postcss-loader'] // 增加 'postcss-loader' , 注意顺序
      }

还要新建一个 postcss.config.js 文件,内容是

module.exports = {
  plugins: [require('autoprefixer')]
}

重新运行 npm run dev 即可看到效果,自动增加了必要的前缀。

抽离 css 文件

默认情况下,webpack 会将 css 代码全部写入到 html 的 <style> 标签中,但是打包代码时需要抽离到单独的 css 文件中。安装 npm i mini-css-extract-plugin -D 然后配置 build/webpack.prod.js(打包代码时才需要,运行时不需要)

// 引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// 增加 webpack 配置
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: [
          MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
          'css-loader',
          'postcss-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/main.[contentHash:8].css'
    })
  ]

如需要压缩 css ,需要安装 npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D ,然后增加配置

// 引入插件
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

// 增加 webpack 配置
  optimization: {
    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
  },

运行 npm run build 即可看到打包出来的 css 是独立的文件,并且是被压缩过的。

处理图片

要在 js 中 import 图片,或者在 css 中设置背景图片。安装 npm i file-loader -D 然后配置 build/webpack.common.js

{
        test: /\.(png|jpg|gif)$/,
        use: 'file-loader'
      }

如果想要处理 html 代码中 <img src="..."/> 的形式,则安装 npm i html-withimg-loader -D 然后配置 build/webpack.common.js

{
        test: /\.html$/,
        use: 'html-withimg-loader'
      }

打包之后,dist 目录下会生成一个类似 917bb63ba2e14fc4aa4170a8a702d9f8.jpg 的文件,并被引入到打包出来的结果中。

如果想要将小图片用 base64 格式产出,则安装 npm i url-loader -D ,然后配置 build/webpack.common.js

{
        test: /\.(png|jpg|gif)$/,
        use: {
          loader: 'url-loader',
          options: {
            // 小于 5kb 的图片用 base64 格式产出
            // 否则,依然延用 file-loader 的形式,产出 url 格式
            limit: 5 * 1024,

            // 打包到 img 目录下
            outputPath: '/img/',

            // 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
            // publicPath: 'http://cdn.abc.com'
          }
        }
      },

多页应用

src 下有 index.js index.html 和 other.js other.html ,要打包输出两个页面,且分别引用各自的 js 文件。

第一,配置输入输出

entry: {
    index: path.join(srcPath, 'index.js'),
    other: path.join(srcPath, 'other.js')
  },
  output: {
    filename: '[name].[contentHash:8].js', // [name] 表示 chunk 的名称,即上面的 index 和 other
    path: distPath
  },

第二,配置 html 插件

plugins: [
    // 生成 index.html
    new HtmlWebpackPlugin({
      template: path.join(srcPath, 'index.html'),
      filename: 'index.html',
      // chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
      chunks: ['index'] // 只引用 index.js
    }),
    // 生成 other.html
    new HtmlWebpackPlugin({
      template: path.join(srcPath, 'other.html'),
      filename: 'other.html',
      chunks: ['other'] // 只引用 other.js
    }),

抽离公共代码

公共模块

多个页面或者入口,如果引用了同一段代码,如上文的多页面例子中,index.js 和 other.js 都引用了 import './common.js' ,则 common.js 应该被作为公共模块打包。webpack v4 开始弃用了 commonChunkPlugin 改用 splitChunks ,可修改 build/webpack.prod.js 中的配置

optimization: {
    // 分割代码块
    splitChunks: {
      // 缓存分组
      cacheGroups: {
        // 公共的模块
        common: {
          chunks: 'initial',
          minSize: 0, // 公共模块的大小限制
          minChunks: 2 // 公共模块最少复用过几次
        }
      }
    }
  },

重新运行 npm run build ,即可看到有 common 模块被单独打包出来,就是 common.js 的内容。

第三方模块

同理,如果我们的代码中引用了 jquery lodash 等,也希望将第三方模块单独打包,和自己开发的业务代码分开。这样每次重新上线时,第三方模块的代码就可以借助浏览器缓存,提高用户访问网页的效率。修改配置文件,增加下面的 vendor: {...} 配置。

optimization: {
    // 分割代码块
    splitChunks: {
      // 缓存分组
      cacheGroups: {
        // 第三方模块
        vendor: {
          priority: 1, // 权限更高,优先抽离,重要!!!
          test: /node_modules/,
          chunks: 'initial',
          minSize: 0, // 大小限制
          minChunks: 1 // 最少复用过几次
        },

        // 公共的模块
        common: {
          chunks: 'initial',
          minSize: 0, // 公共模块的大小限制
          minChunks: 2 // 公共模块最少复用过几次
        }
      }
    }
  },

重启 npm run build ,即可看到 vendor 模块被打包出来,里面是 jquery 或者 lodash 等第三方模块的内容。

懒加载

webpack 支持使用 import(...) 语法进行资源懒加载。安装 npm i @babel/plugin-syntax-dynamic-import -D 然后将插件配置到 .babelrc 中。

新建 src/dynamic-data.js 用于测试,内容是 export default { message: 'this is dynamic' } 。然后在 src/index.js 中加入

setTimeout(() => {
  import('./dynamic-data.js').then(res => {
    console.log(res.default.message) // 注意这里的 default
  })
}, 1500)

重新运行 npm run dev 刷新页面,可以看到 1.5s 之后打印出 this is dynamic 。而且,dynamic-data.js 也是 1.5s 之后被加载进浏览器的 —— 懒加载,虽然文件名变了。

重新运行 npm run build 也可以看到 dynamic-data.js 的内容被打包一个单独的文件中。

常见性能优化

tree shaking

使用 import 引入,在 production 环境下,webpack 会自动触发 tree shaking ,去掉无用代码。但是使用 require 引入时,则不会触发 tree shaking。这是因为 require 是动态引入,无法在编译时判断哪些功能被使用。而 import 是静态引入,编译时即可判断依赖关系。

noParse

不去解析某些 lib 其内部的依赖,即确定这些 lib 没有其他依赖,提高解析速度。可配置到 build/wepback.common.js 中

module: {
    noParse: /jquery|lodash/, // 不解析 jquery 和 lodash 的内部依赖

ignorePlugin

以常用的 moment 为例。安装 npm i moment -d 并且 import moment from 'moment' 之后,monent 默认将所有语言的 js 都加载进来,使得打包文件过大。可以通过 ignorePlugin 插件忽略 locale 下的语言文件,不打包进来。

plugins: [
    new webpack.IgnorePlugin(/\.\/locale/, /moment/), // 忽略 moment 下的 /locale 目录

这样,使用时可以手动引入中文包,并设置语言

import moment from 'moment'
import 'moment/locale/zh-cn' // 手动引入中文语言包
moment.locale('zh-cn')
const r = moment().endOf('day').fromNow()
console.log(r)

happyPack

多进程打包,参考 https://www.npmjs.com/package/happypack 。注意,小项目使用反而会变慢。只有项目较大,打包出现明显瓶颈时,才考虑使用 happypack 。

常用插件和配置

ProvidePlugin

如要给所有的 js 模块直接使用 $ ,不用每次都 import $ from 'jquery' ,可做如下配置

plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery'
    }),

externals

如果 jquery 已经在 html 中通过 cdn 引用了,无需再打包,可做如下配置

externals: {
    jquery: 'jQuery'
  },

alias

设置 alias 别名在实际开发中比较常用,尤其是项目较大,目录较多时。可做如下配置

resolve: {
    alias: {
      Utilities: path.join(srcPath, 'utilities')
    }
  },

在该配置之前,可能需要 import Utility from '../../utilities/utility' 使用。配置之后就可以 import Utility from 'Utilities/utility' 使用,一来书写简洁,二来不用再考虑相对目录的层级关系。

extensions

如果引用文件时没有写后缀名,可以通过 extensions 来匹配。

resolve: {
    extensions: [".js", ".json"]
  },

clean-webpack-plugin

由于使用了 contentHash ,每次 build 时候都可能打包出不同的文件,因此要及时清理 dist 目录。安装 npm i clean-webpack-plugin -D ,然后在 build/webpack.prod.js 中配置

// 引入插件
const CleanWebpackPlugin = require('clean-webpack-plugin')

// 增加配置
  plugins: [
    new CleanWebpackPlugin(), // 默认清空 output.path 目录

copy-webpack-plugin

build 时,将 src 目录下某个文件或者文件夹,无条件的拷贝到 dist 目录下,例如 src/doc 目录拷贝过去。安装 npm i copy-webpack-plugin -D,然后在 build/webpack.prod.js 中配置

// 引入插件
const CopyWebpackPlugin = require('copy-webpack-plugin')

// 增加配置
  plugins: [
    new CopyWebpackPlugin([
      {
        from: path.join(srcPath, 'doc'), // 将 src/doc 拷贝到 dist/doc
        to: path.join(distPath, 'doc')
      }
    ]),

bannerPlugin

代码的版权声明,在 build/webpack.prod.js 中配置即可。

plugins: [
    new webpack.BannerPlugin('by github.com/wangfupeng1988 \r'),

总结

webpack 发展至今配置非常多,该视频中也没有全部讲解出来,只是一些实际开发中常用的。其他的配置可以去看官网文档。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
关于javascript event flow 的一个bug详解
Sep 17 Javascript
Jquery Ajax方法传值到action的方法
May 11 Javascript
node.js中的emitter.emit方法使用说明
Dec 10 Javascript
JavaScript 模块化编程(笔记)
Apr 08 Javascript
跟我学习javascript的prototype,getPrototypeOf和__proto__
Nov 17 Javascript
Node.js 条形码识别程序构建思路详解
Feb 14 Javascript
zTree插件下拉树使用入门教程
Apr 11 Javascript
微信浏览器禁止页面下拉查看网址实例详解
Jun 28 Javascript
JS返回顶部实例代码
Aug 09 Javascript
js实现一个简易计算器
Mar 30 Javascript
Vue实现跑马灯效果
May 25 Javascript
JavaScript流程控制(循环)
Dec 06 Javascript
详解ES6 export default 和 import语句中的解构赋值
May 28 #Javascript
jQuery实现高级检索功能
May 28 #jQuery
利用原生JS实现data方法示例代码
May 28 #Javascript
php结合js实现多条件组合查询
May 28 #Javascript
vue实现前台列表数据过滤搜索、分页效果
May 28 #Javascript
js 将线性数据转为树形的示例代码
May 28 #Javascript
React中使用外部样式的3种方式(小结)
May 28 #Javascript
You might like
php获取百度收录、百度热词及百度快照的方法
2015/04/02 PHP
PHP的全局错误处理详解
2016/04/25 PHP
JavaScript 判断浏览器类型及版本
2009/02/21 Javascript
js 使用form表单select类实现级联菜单效果
2012/12/19 Javascript
javascript代码运行不出来执行错误的可能情况整理
2013/10/18 Javascript
JavaScript Serializer序列化时间处理示例
2014/07/31 Javascript
jquery选择器需要注意的问题
2014/11/26 Javascript
js分页工具实例
2015/01/28 Javascript
input输入框鼠标焦点提示信息
2015/03/17 Javascript
Position属性之relative用法
2015/12/14 Javascript
Bootstrap项目实战之首页内容介绍(全)
2016/04/25 Javascript
Javascript 事件冒泡机制详细介绍
2016/10/10 Javascript
JS实现的简单拖拽功能示例
2017/03/13 Javascript
Vue2.0 UI框架ElementUI使用方法详解
2017/04/14 Javascript
node文件上传功能简易实现代码
2017/06/16 Javascript
Mongoose实现虚拟字段查询的方法详解
2017/08/15 Javascript
Windows安装Node.js报错:2503、2502的解决方法
2017/10/25 Javascript
JS写XSS cookie stealer来窃取密码的步骤详解
2017/11/20 Javascript
JS实现的抛物线运动效果示例
2018/01/30 Javascript
JavaScript设计模式之构造器模式(生成器模式)定义与用法实例分析
2018/07/26 Javascript
微信小程序实现左右列表联动
2020/05/19 Javascript
分析在Python中何种情况下需要使用断言
2015/04/01 Python
深入理解python函数递归和生成器
2016/06/06 Python
python与caffe改变通道顺序的方法
2018/08/04 Python
关于numpy中eye和identity的区别详解
2019/11/29 Python
Python如何实现的二分查找算法
2020/05/27 Python
python利用google翻译方法实例(翻译字幕文件)
2020/09/21 Python
BONIA官方网站:国际奢侈品牌和皮革专家
2016/11/27 全球购物
荷兰皇家航空公司官方网站:KLM Royal Dutch Airlines
2017/12/07 全球购物
哥伦比亚加拿大官网:Columbia Sportswear Canada
2020/09/07 全球购物
JRE、JDK、JVM之间的关系怎样
2012/05/16 面试题
电气工程及其自动化自我评价四篇
2013/09/24 职场文书
法人身份证明书
2014/10/08 职场文书
师德标兵先进事迹材料
2014/12/19 职场文书
四年级小学生评语
2014/12/26 职场文书
教师培训简讯
2015/07/20 职场文书