我在 Windows 下安装 Akeneo PIM 时遇到问题;
我跟进了akeneo-install-instruction(pim-community-standard-v2.3)
https://docs.akeneo.com/2.3/install_pim/manual/installation_ce_archive.html
并对 package.json 进行必要的修改,就像这里所说的: Akeneo 安装 / NODE_PATH=node_modules notrecognized / yarn run webpack Error
然后我添加了 webpack,就像在这里完成的那样:
https ://webkul.com/blog/yarn-run-webpack-issues-in-akeneo/
和:
npm install --save-dev webpack
npm install
但我总是在开始后得到错误:
yarn run webpack
./web/bundles/pimenrich/js/catalog-volume/header.ts 中的错误模块解析失败:C:\xampp\htdocs\pim-community-standard\web\bundles\pimenrich\js\catalog-volume\header。 ts Unexpected token (1:16) 您可能需要适当的加载程序来处理此文件类型。| 导入 BaseView = require('pimenrich/js/view/base'); | 从'下划线'导入*作为_;| @ ./web/js/module-registry.js 2:19263-19307 @ ./vendor/akeneo/pim-community-dev/webpack/require-context.js @ ./web/bundles/pimenrich/js/form/ builder.js @ ./web/bundles/pimenrich/js/index.js @ multi babel-polyfill ./web/bundles/pimenrich/js/index.js ...
我究竟做错了什么?
更新: webpack.config.js 文件的内容:
/* eslint-env es6 */
const fs = require('fs');
const process = require('process');
const rootDir = process.cwd();
const webpack = require('webpack');
const path = require('path');
const _ = require('lodash');
const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const isProd = process.argv && process.argv.indexOf('--env=prod') > -1;
const sourcePath = path.join(rootDir, 'web/js/require-paths.js');
if (!fs.existsSync(sourcePath)) {
throw new Error(`The web/js/require-paths.js module does not exist - You need to run
"bin/console pim:install" or "bin/console pim:installer:dump-require-paths" before
running webpack \n`);
}
const {getModulePaths, createModuleRegistry} = require('./webpack/requirejs-utils');
const {aliases, config} = getModulePaths(rootDir, __dirname, sourcePath);
createModuleRegistry(Object.keys(aliases), rootDir);
const babelPresets = [
[
'babel-preset-env',
{
targets: {
browsers: ['firefox >= 45'],
},
},
],
];
if (isProd) {
babelPresets.push('babel-preset-minify');
}
console.log('Starting webpack from', rootDir, 'in', isProd ? 'prod' : 'dev', 'mode');
module.exports = {
stats: {
hash: false,
maxModules: 5,
modules: false,
timings: true,
version: true,
},
target: 'web',
entry: ['babel-polyfill', path.resolve(rootDir, './web/bundles/pimenrich/js/index.js')],
output: {
path: path.resolve('./web/dist/'),
publicPath: '/dist/',
filename: '[name].min.js',
chunkFilename: '[name].bundle.js',
},
devtool: 'source-map',
resolve: {
symlinks: false,
alias: _.mapKeys(aliases, (path, key) => `${key}$`),
modules: [path.resolve('./web/bundles'), path.resolve('./node_modules')],
extensions: ['.js', '.json', '.ts', '.tsx'],
},
module: {
rules: [
// Inject the module config (to replace module.config() from requirejs)
{
test: /\.js$/,
exclude: /\/node_modules\/|\/spec\//,
use: [
{
loader: path.resolve(__dirname, 'webpack/config-loader'),
options: {
configMap: config,
},
},
],
},
// Load html without needing to prefix the requires with 'text!'
{
test: /\.html$/,
exclude: /node_modules|spec/,
use: [
{
loader: 'raw-loader',
options: {},
},
],
},
// Expose the Backbone variable to window
{
test: /node_modules\/backbone\/backbone.js/,
use: [
{
loader: 'expose-loader',
options: 'Backbone',
},
],
},
{
test: /node_modules\/backbone\/backbone.js/,
use: [
{
loader: 'imports-loader',
options: 'this=>window',
},
],
},
{
test: /node_modules\/summernote\/dist\/summernote.js/,
use: [
{
loader: 'imports-loader',
options: 'require=>function(){}',
},
{
loader: 'imports-loader',
options: 'require.specified=>function(){}',
},
],
},
// Expose jQuery to window
{
test: /node_modules\/jquery\/dist\/jquery.js/,
use: [
{
loader: 'expose-loader',
options: 'jQuery',
},
{
loader: 'expose-loader',
options: '$',
},
],
},
// Expose the require-polyfill to window
{
test: path.resolve(__dirname, './webpack/require-polyfill.js'),
use: [
{
loader: 'expose-loader',
options: 'require',
},
],
},
// Process the pim webpack files with babel
{
test: /\.js$/,
include: /(web\/bundles|webpack|spec)/,
exclude: /lib|node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: babelPresets,
cacheDirectory: 'web/cache',
},
},
},
// Process the typescript loader files
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
context: path.resolve(rootDir),
},
},
{
loader: path.resolve(__dirname, 'webpack/config-loader'),
options: {
configMap: config,
},
},
],
include: /(web\/bundles)/,
exclude: /lib|node_modules|vendor|tests|src|packages/,
},
],
},
watchOptions: {
ignored: /node_modules|app|app\/cache|vendor/,
},
// Support old loader declarations
resolveLoader: {
moduleExtensions: ['-loader'],
},
plugins: [
// Clean up the dist folder and source maps before rebuild
new WebpackCleanupPlugin(),
// Map modules to variables for global use
new webpack.ProvidePlugin({_: 'underscore', Backbone: 'backbone', $: 'jquery', jQuery: 'jquery'}),
// Ignore these directories when webpack watches for changes
new webpack.WatchIgnorePlugin([
path.resolve(rootDir, './node_modules'),
path.resolve(rootDir, './app'),
path.resolve(rootDir, './app/cache'),
path.resolve(rootDir, './vendor'),
]),
// Inject live reload to auto refresh the page (hmr not compatible with our app)
new LiveReloadPlugin({appendScriptTag: true, ignore: /node_modules/}),
// Split the app into chunks for performance
new webpack.optimize.CommonsChunkPlugin({
name: 'lib',
minChunks: module => module.context && module.context.indexOf('lib') !== -1,
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.indexOf('node_modules') !== -1,
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': isProd ? JSON.stringify('production') : JSON.stringify('development'),
}),
new webpack.optimize.CommonsChunkPlugin({name: 'manifest'}),
],
};
更新: web\bundles\pimenrich\js\view\base.ts 文件的内容:
import * as _ from 'underscore';
import * as JQuery from 'jquery';
import * as Backbone from 'backbone';
import View from 'pimenrich/js/view/base-interface';
const mediator = require('oro/mediator');
/**
* View base class
*
* @author Julien Sanchez <julien@akeneo.com>
* @author Filips Alpe <filips@akeneo.com>
* @copyright 2015 Akeneo SAS (http://www.akeneo.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
class BaseView extends Backbone.View<any> implements View {
private parent: View | null = null;
private extensions: {[code: string]: View};
readonly preUpdateEventName: string = 'pim_enrich:form:entity:pre_update';
readonly postUpdateEventName: string = 'pim_enrich:form:entity:post_update';
public code: string = 'form';
public configured: boolean;
public zones: {[code: string]: any};
public targetZone: string;
public position: number;
/**
* {@inheritdoc}
*/
constructor(config: any) {
super(config);
this.extensions = {};
this.zones = {};
this.targetZone = '';
this.configured = false;
}
/**
* Configure the extension and its child extensions
*
* @return {JQueryPromise<any>}
*/
configure(): JQueryPromise<any> {
if (null === this.parent) {
this.model = new Backbone.Model();
}
const extensionPromises = Object.values(this.extensions).map((extension: View) => {
return extension.configure();
});
return JQuery.when(...extensionPromises).then(() => {
this.configured = true;
});
}
/**
* Add a child extension to this extension
*
* @param {string} code Extension's code
* @param {View} extension Backbone module of the extension
* @param {string} zone Targeted zone
* @param {number} position The position of the extension
*/
addExtension(code: string, extension: View, zone: string, position: number) {
extension.setParent(this);
extension.code = code;
extension.targetZone = zone;
extension.position = position;
if (undefined === this.extensions || null === this.extensions) {
throw 'this.extensions have to be defined. Please ensure you called parent initialize() method.';
}
this.extensions[code] = extension;
}
/**
* Get a child extension (the first extension matching the given code or ends with the given code)
*
* @param {string} code
* @return {View}
*/
getExtension(code: string): View {
const extensionKey = _.findKey(this.extensions, (extension: View) => {
const expectedPosition = extension.code.length - code.length;
return expectedPosition >= 0 && expectedPosition === extension.code.indexOf(code, expectedPosition);
});
return this.extensions[extensionKey];
}
/**
* Set the parent of this extension
*
* @param {View} parent
*/
setParent(parent: View) {
this.parent = parent;
}
/**
* Get the parent of the extension
*
* @return {View | null}
*/
getParent(): View | null {
return this.parent;
}
/**
* Get the root extension
*
* @return {View}
*/
getRoot(): View {
let rootView = <View>this;
let parent = this.getParent();
while (null !== parent) {
rootView = parent;
parent = parent.getParent();
}
return rootView;
}
/**
* Set data in the root model
*
* @param {any} data
* @param {{silent?: boolean}} options If silent is set to true, don't fire events
* pim_enrich:form:entity:pre_update and pim_enrich:form:entity:post_update
*/
setData(data: any, options: {silent?: boolean} = {}) {
if (!options.silent) {
this.getRoot().trigger(this.preUpdateEventName, data);
}
this.getRoot().model.set(data, options);
if (!options.silent) {
this.getRoot().trigger(this.postUpdateEventName, data);
}
}
/**
* Get the form raw data (vanilla javascript object)
*
* @return {any}
*/
getFormData(): any {
return this.getRoot().model.toJSON();
}
/**
* Get the form data (backbone model)
*
* @return {Backbone.Model}
*/
getFormModel(): Backbone.Model {
return this.getRoot().model;
}
/**
* Called before removing the form from the view
*/
shutdown() {
this.doShutdown();
Object.values(this.extensions).forEach((extension: View) => extension.shutdown());
}
/**
* The actual shutdown method called on all extensions
*/
doShutdown() {
this.stopListening();
this.undelegateEvents();
this.$el.removeData().unbind();
this.remove();
Backbone.View.prototype.remove.call(this);
}
/**
* {@inheritdoc}
*/
render(): View {
if (!this.configured) {
return this;
}
return this.renderExtensions();
}
/**
* Render the child extensions
*
* @return {View}
*/
renderExtensions(): View {
// If the view is no longer attached to the DOM, don't render the extensions
if (undefined === this.el) {
return this;
}
this.initializeDropZones();
Object.values(this.extensions).forEach((extension: View) => {
this.renderExtension(extension);
});
return this;
}
/**
* Render a single extension
*
* @param {View} extension
*/
renderExtension(extension: View) {
var zone = this.getZone(extension.targetZone);
if (null === zone) {
throw new Error(
`Can not render extension "${extension.code}" in "${this.code}": zone "${extension.targetZone}" does not exist`
);
}
zone.appendChild(extension.el);
extension.render();
}
/**
* Initialize dropzone cache
*/
initializeDropZones() {
this.zones = this.$('[data-drop-zone]')
.toArray()
.reduce((zones: {[code: string]: HTMLElement}, zone: HTMLElement) => {
return {...zones, [<string>zone.dataset.dropZone]: zone};
}, {});
this.zones['self'] = this.el;
}
/**
* Get the drop zone for the given code
*
* @param {string} code
*
* @return {HTMLElement | null}
*/
getZone(code: string): HTMLElement | null {
if (!(code in this.zones)) {
this.zones[code] = this.el.querySelector(`[data-drop-zone="${code}"]`);
}
if (!this.zones[code]) {
return null;
}
return this.zones[code];
}
/**
* Trigger event on each child extensions and their childs
*/
triggerExtensions() {
const options = Object.values(arguments);
Object.values(this.extensions).forEach(extension => {
extension.trigger.apply(extension, options);
extension.triggerExtensions.apply(extension, options);
});
}
/**
* Listen on child extensions and their childs events
*
* @param {string} code
* @param {Function} callback
*/
onExtensions(code: string, callback: any) {
Object.values(this.extensions).forEach((extension: View) => {
this.listenTo(extension, code, callback);
});
}
/**
* Get the root form code
*
* @return {string}
*/
getFormCode(): string {
return this.getRoot().code;
}
/**
* Listen to given mediator events to trigger them locally (in the local root).
* This way, extensions attached to this form don't have to listen "globally" on the mediator.
*
* @param {{[mediatorEvent: string]: string}} mediator events to forward:
* {'mediator:event:name': 'this:event:name', ...}
*/
forwardMediatorEvents(events: {[mediatorEvent: string]: string}) {
Object.keys(events).forEach((localEvent: string) => {
const mediatorEvent = events[localEvent];
this.listenTo(mediator, mediatorEvent, (...args: any[]) => {
this.trigger(localEvent, ...args);
});
});
}
}
export = BaseView;